引入 JS

HTML中引入JS的四种方式:

  1. 头部引入js

    1. <head>
    2. <title>Document</title>
    3. <script type="text/javascript">
    4. // ... js 代码
    5. </script>
    6. </head>
  2. 页面主体引入js

    1. <!DOCTYPE html>
    2. <html xmlns="https://www.w3.org/1999/xhtml">
    3. <head>
    4. <title>Document</title>
    5. </head>
    6. <script type="text/javascript">
    7. // ...js 代码
    8. </script>
    9. </html>
  3. 元素事件中直接编写

    1. <input type="button" onClick="alert(Hello world)" value="Click me"/>
  4. 引入外部js

    1. <script src="./js/index.js" type="text/javascript"></script>

一、严格模式

在全局或者函数的第一条语句写:'usestrict'

语法行为就变为:

  • 声明变量必须带上var等关键字
  • 自定义函数中的this指向window对象
  • 对象不能有重名的属性

二、数据类型

2.1 7种基本类型

JavaScript - 图1

2.2 对象类型

  1. Object:任意对象,用于存储对象的数据,是一种无序数据集合
    属性的访问:
    • .属性名
    • [属性名],属性名本质就是字符串!
  2. Function:一种特别的对象,用于存储代码
  3. Array:有序的数据集合

2.3 判断

  1. typeof:返回数据类型的字符串表达式 | 类型 | tyeof | | —- | —- | | object | object | | function | function | | array | object | | string | string | | number | number | | boolean | boolean | | undefined | undefined | | null | object |

  2. instanceof:用于判断一个对象实例的具体类型

  3. ==:值是否相等
    ===:值和类型是否相等

2.4 拓展运算符

两个作用:

  1. 打包。将一系列数据集合到一个数组中,常用于函数传递参数

    1. // args中集合了传入fn函数的所有数据
    2. function fn(...args) {
    3. // ...
    4. }
  2. 解包。将一个数据集合拆分成一系列逗号分隔的独立数据

三、基本语法

3.1 变量解构赋值

解构失败的时候得到的值是undefined. 注意nullundefined的区别:

  • undefined是指某个变量没有初始值,本身就是undefined类型。原本应该有值但是没有赋值,或者本身就没有定义
  • null是指一个空对象,类型为object

3.1.1 数组解构

  1. let [x, y] = [1, 2, 3]
  2. x // 1
  3. y // 2

只要某种数据解构具有Iterator接口就都可以使用数组解构

  1. let [x, y, z] = new Set(['a', 'b', 'c'])

3.1.2 对象解构

数组的解构是按照次序排列的,变量的值由其位置决定;对象的解构没有次序之分,按照变量名进行匹配取得正确的值。

  1. let {bar, foo} = {foo: 'aaa', bar: 'bbb'}
  1. const {log} = console
  2. log('hello')

对象解构的本质就是对象拓展的简写:src:dst

  1. let {foo: foo, bar: bar} = {
  2. foo: 'aaa',
  3. bar: 'bbb'
  4. }
  1. let {foo: bar} = {
  2. foo: 'aaa',
  3. bar: 'bbb'
  4. }
  5. bar // 'aaa'
  6. foo // error: foo is not defined

四、函数

4.1 函数定义

  1. 关键字声明

    1. function fn() {...}
  2. 表达式声明 ```javascript let fn = function() {…}

function Fn() {…} let fn = new Fn()

  1. 3. 箭头函数
  2. ```javascript
  3. let fn = () => {...}
  1. Immediately Invoked Function Expression
    立即调用函数,内部的变量外部不可见(即使是var类型)
    1. let a = (function() {
    2. var name = "Beney"
    3. console.log(name)
    4. return name;
    5. })()
    6. a // Beney
    7. name // throws "Uncaught ReferenceError..."

4.2 函数调用

函数名()

this问题👏

this指向最终调用这个函数的对象,一切皆对象,函数必然是由某个对象调用。(更具体的可以说是,一切都是函数对象,所有对象都是由构造函数创建)

bind,apply,call 区别

都是用于绑定函数的this对象,即绑定函数的调用者。

  • bind:fn.bind(x),返回一个绑定到x对象的函数。
  • apply:fn.apply(x, [argsArray]),接收一个参数数组,绑定到x对象,并执行函数
  • call:fn.call(x, [arg1, [arg2, [...]]]),接收一系列参数,绑定到x对象,并执行函数

箭头函数的this

ES6 中的箭头函数 this 始终指向函数定义时的 this,而非执行时。

箭头函数没有this绑定,必须通过查找作用域链来决定其值,如果箭头函数被非箭头函数包含,则this绑定到最近一层非箭头函数的this,否则this为undefined。

  1. let a = {
  2. name: "Beney",
  3. fn2: function() {
  4. console.log(this.name)
  5. },
  6. fn2: function() {
  7. setTimeout(() => {
  8. this.fn1() // 最近一层的函数的this为a对象
  9. }, 100)
  10. }
  11. }
  12. a.fn2() // Beney

使用变量存储this

若不使用 ES6 则使用这种方式最为简单,先将调用本函数的对象存储到一个变量 _this,然后函数中都使用这个 _this,就保证了调用对象不会突然改变。

  1. let a = {
  2. name: "Beney",
  3. fn1: function() {
  4. console.log(this.name)
  5. },
  6. fn2: function() {
  7. let _this = this // 使用变量存储this
  8. setTimeout(function() {
  9. _this.fn1()
  10. }, 100)
  11. }
  12. }
  13. a.fn2() // Beney

回调函数

声明了之后供别处调用的函数,常用作参数传递

五、函数进阶🔰

5.1 原型

函数的 prototype 属性

函数定义时,默认添加 prototype 属性

每一个一个函数对象**都有一个** prototype(显式原型),默认指向一个空的Object对象(除了Object)。

这个 prototype 对象就是之后创建该函数实例对象的爸爸,prototype 中的所有方法都可以供实例对象使用

函数对象和其原型对象为相互引用的关系:

  • 函数对象的 prototype 指向原型对象
  • 原型对象的 constructor 指向函数对象
  1. function Fn () {
  2. // 内部默认语句: Fn.prototype = {}
  3. }
  4. console.log(Fn.prototype.constructor === Fn) // true

JavaScript - 图2

实例的 proto

实例对象创建时,默认添加 __proto__

每一个实例对象(不仅函数实例)都有一个 __proto__ 属性,隐式原型。

实例对象的隐式原型等于其构造函数的显式原型,即都指向一个统一的模板——原型对象

  1. // 构造函数
  2. function Fn () {
  3. }
  4. // 实例化一个对象
  5. let fun = new Fn() // 内部默认语句:this.__proto__ = Fn.prototype
  6. console.log(fun.__proto__ === Fn.prototype) // true

内存关系图

  1. function Fn() {
  2. }
  3. let fn = new Fn()
  4. Fn.prototype.test = () => {...}

image.png

总结:

  • 函数的 prototype 属性,在定义的时候自动添加,默认值为一个空的Object对象。
  • 函数的原型对象和函数相互引用。
  • 实例对象proto 属性,在创建实例的时候自动添加,默认值为构造函数的原型对象。
  • 所有原型对象,都可以看作是实例对象的一个父类,给实例对象提供了可以调用的方法。类似Java中的抽象类,用于同一份代码的复用

JavaScript - 图4

  1. function Fn() { // Fn.prototype = {}
  2. }
  3. let fn = new Fn() // fn.__proto__ = Fn.prototype

5.2 原型链

原型链本质是隐式原型链,其原理如下:

  • 访问一个对象的**属性**时
    • 先在当前对象的属性中查找,找到则返回。
    • 否则,一直沿着 proto 在其原型对象中查找,如此套娃,直至找到属性
    • 若始终没有找到,返回undefined

原型链:查找对象属性

  1. function Fn () {
  2. }
  3. let fn = new Fn()
  4. fn.toString()

若仅关注对 toString 方法的查找:

JavaScript - 图5

若关注全局的关系:

  • function Fn() 本质为 var Fn = new Function()
  • 所有函数都是由 Function 创建,所以所有函数proto 都是Function Prototype
    对象如Object也是如此,因为 Object 也是一个函数,用于创建对象
    再次强调:所有函数的 proto 都指向 Function Prototyp
  • 特例:Function 函数本身也是由 Function 创建,即 Function 既是实例又是构造函数,所以 Function 的 proto 和 prototype 都指向 Function Prototype

JavaScript - 图6

原型继承

  • 构造函数的实例对象自动拥有构造函数原型对象的所有属性(方法),实例就类似一个子类继承构造方法的原型。
  • 本质利用的就是隐式原型链。

原型属性问题

  • 读取对象的属性时,会自动到原型链中查找。
  • 设置对象属性时,不会查找原型链。若当前对象没有此属性,则直接添加此属性并设置值。
  • 方法一般定义在原型中方便复用;属性一般通过构造函数定义在实例本身上。
  1. function Fn() {
  2. this.test1 = () => {...} // 每一个实例独享一个test1
  3. }
  4. Fn.prototype.test2 = () => {...} // 所有实例共享一个test2

JavaScript - 图7

instanceof

表达式:A instanceof B

若 B Prototype在 A 实例的原型链上,则返回 true,否则为 false。

JavaScript - 图8

小测试

结合之前的全局关系图可以解决

  1. function Fn() {}
  2. Object.prototype.a = () => {
  3. console.log('a()')
  4. }
  5. Function.prototype.b = () => {
  6. console.log('b()')
  7. }
  8. var fn = new Fn()
  9. fn.a() // a()
  10. fn.b() // Uncaught TypeError,原型链上找不到b
  11. Fn.a() // a()
  12. Fn.b() // b()
  13. // fn --> Fn.prototype --> Object.prototype
  14. // Fn --> Function.prototype --> Object.prototype

细节总结

  1. 函数的显式原型都是指向一个空的 Object 实例对象,除了 Object,因为Object.prototype.__proto__ === null,故 Object Prototype 并不是一个 Object 空对象实例。
  2. 所有函数都是 Function 的实例,包括 Function 本身。
  3. Object Prototype 是原型链的尽头,再往上就是 null 了。
  4. 判断实例是否是某个函数的实例,只需要比较实例的隐式原型链,和函数的显式原型链是否相等。

5.3 执行上下文

执行上下文栈

全局代码执行前,JS 引擎就会创建一个栈来管理所有的上下文对象。一旦执行就会先创建 window 全局上下文

  1. 创建准备执行的上下文
  2. 预处理准备:形参取值,提升变量,提升函数,this,arguments
  3. 执行函数体语句

window 的执行上下文就相当于 Java 中的 main 函数执行栈帧的概念。所以一开始运行 JS 代码,就有全局的window 上下文。

所谓的上下文可以看作是一个函数执行栈,一个函数执行产生一个栈帧。

JavaScript - 图9

变量声明提升

通过 var 定义的变量,其**声明语句会被自动提升到当前上下文域内的**最上方先执行。

  1. function fn() {
  2. // ...
  3. var a = 3
  4. }
  1. function fn() {
  2. var a // **undefined**
  3. //...
  4. a = 3
  5. }
  6. console.log(a) // a的作用域之外,报错
  7. // Uncaught ReferenceError: a is not defined

函数声明提升

函数对象的声明也会自动提升到作用域的最上方执行。函数定义就是创建了一个函数对象。

所以函数定义之前,就可以调用该函数。

  1. fn() // hello
  2. function fn () {
  3. console.log('hello')
  4. }
  1. fn() // 本质是变量提升,所以会报错!
  2. var fn = function () {...}

note:变量提升先于函数提升。

细节

同名函数、变量同时提升

  1. var c = 1
  2. function c(c) {
  3. console.log(c)
  4. }
  5. c(2) // Uncaught TypeError: c is not a function

以上写法经过提升后实际的执行顺序:

  1. var c
  2. function c(c) {
  3. console.log(c)
  4. }
  5. c = 1
  6. c(2) // 现在的报错原因就一目而了然

5.4 作用域

作用域分类:

  • 全局作用域
  • 函数作用域
  • 块作用域,let,const

作用域指变量能够被访问到的一个范围,是一个静态的概念,函数定义好之后就一直存在,不会再改变。

作用域 上下文
函数定义时就确定 一个函数执行创建一个上下文
静态概念
,确定后不会改变
动态概念,函数调用时创建,函数结束时销毁
代码层面的概念 底层执行的概念

作用域链

作用域链的意思是:在当前作用域中查找不到某个变量的时候,会向外在更大的一层作用域中查找变量。

  1. var a = 2
  2. function fn1() {
  3. var b = 3
  4. function fn2() {
  5. var c = 4
  6. console.log(c)
  7. console.log(b) // 向外一层作用域
  8. console.log(a) // 向外两层作用域
  9. }
  10. fn2()
  11. }
  12. fn1()

JavaScript - 图10

案例理解

案例1

  1. var x = 10
  2. function fn() {
  3. console.log(x)
  4. }
  5. function show(f) {
  6. var x = 20
  7. f()
  8. }
  9. show(fn)

JavaScript - 图11

案例2

  1. var fn = function () {
  2. console.log(fn)
  3. }
  4. fn()

JavaScript - 图12

案例3

  1. var obj = {
  2. fn2: function () {
  3. console.log(fn2)
  4. }
  5. }
  6. obj.fn2()

JavaScript - 图13

案例3修正

  1. var obj = {
  2. fn2: function () {
  3. console.log(this.fn2) // 使用this
  4. }
  5. }
  6. obj.fn2()

案例4,给按钮添加监听

  1. let btns = document.getElementsByTagName('button')
  2. for (let i = 0; i < btns.length; ++i) {
  3. let btn = btns[i]
  4. btn.onclick = () => {
  5. alert(`第${i}个btn`)
  6. }
  7. } // let为块级作用域,能够正常显示
  1. let btns = document.getElementsByTagName('button')
  2. for (var i = 0, length = btns.length; i < length; ++i) {
  3. let btn = btns[i]
  4. btn.onclick = () => {
  5. alert(`第${i}个btn`)
  6. }
  7. }
  8. // var 为全局作用域,由于在点击按钮的时候循环已经执行完
  9. // 故 i == length,所以无论点击哪一个按钮 i 都是 length 值

总结:let、const 是块级作用域,var 是全局作用域,作用域的不同使得变量提升的程度不同。

六、闭包

6.1 是啥

闭包就是变量的集合对象,内嵌子函数引用其父函数的变量时就可以产生闭包。

产生条件:

  1. 嵌套函数
  2. 内部函数引用了外部函数的数据
  3. 外部函数调用,内部函数定义执行后(内部函数调用之前)
  4. 闭包中包含的是变量

闭包用于在函数内部封装一些变量,并存储变量的状态,使得外部不可见。也可以看作是将一个操作的执行分成多个阶段,拆分成多个套娃函数一步步调用。

封装变量的本质是利用函数的作用域对于外部不可访问。

  1. function a() {
  2. const name = "Beney"
  3. return function b() {
  4. console.log(name) // 引用了外部函数的变量
  5. }
  6. }
  7. a()() // Beney,内部的name外部无法访问

通过Chrome的开发者工具可以很方便观察到。

JavaScript - 图14

6.2 闭包作用

  1. 使得外部函数的局部变量在外部函数执行完后,仍然存活在内存中。延长了局部变量的生命周期。(外部函数每执行一次,产生一个闭包)
  2. 外部函数可以操作内部函数引用的数据。

局部变量能够保存的本质:内部函数对象一直存在,且函数对象内引用着外部函数的局部变量。 所以包含闭包的函数变成垃圾对象,闭包才死亡。

JavaScript - 图15

6.3 闭包缺点

  • 函数执行完后,内部的局部变量没有释放,占用内存的时间较长。
  • 若忘记释放对内部函数的引用,就会导致内存泄漏。

JavaScript - 图16

六、AJAX

Asynchronous Javascript and XML,是一种提高页面更新性能的 Web 技术,性能提高处主要在于对页面的部分更新,就是一种异步更新技术。

在此之前,页面只能全部更新,开销比较大。所以使用 AJAX 能够提高 B/S 的体验。但是现今 AJAX 由于其编写繁杂,可以使用浏览器的原生 api fetch 来替代

  1. fetch(url, {
  2. method: 'POST',
  3. headers: {KV设置http头部的参数},
  4. body: JSON.stringify({要传入http主体的数据}),
  5. // ... 以上都是http报文的参数配置
  6. }).then(async resp => {
  7. const data = await resp.json();
  8. console("receive data:", data);
  9. })
  1. /**
  2. * post方式请求
  3. * @param url {string} 请求地址
  4. * @param payload {Object} 数据内容
  5. * @return {Promise<Response>}
  6. */
  7. export const doPost = async (url, payload) => {
  8. return await fetch(url, {
  9. method: "POST",
  10. credentials: "include",
  11. headers: {
  12. "Content-Type": "application/json;charset=UTF-8",
  13. },
  14. body: JSON.stringify(payload),
  15. });
  16. };

七、常用类型API

7.1 Array

  • push(…elem):尾部添加元素
  • pop():删除尾部元素
  • unshift(…elem):首部添加元素
  • shift():删除首部元素
  • splice(pos, cnt):指定位置删除指定个元素
  • slice(i1, i2):返回[i1, i2)的浅拷贝,无参默认全部取出
  • concat(Array):返回拼接后的新数组,原数组不受影响
  • join(string):当前Array的每一个元素之间用指定的字符串相连,返回最终的结果字符串
  • map(callback(elm, index, array)):数组的每一个元素重新映射,返回一个新的数组
  • filter(callback(elm, index, array)):过滤数组元素,callback返回true则保留该元素,返回一个新的数组
  • reduce(callback(acc, elm, index, array)):每一次计算的值累积到acc中,最后返回acc