复习原生 js 知识
1. this 指向问题里的 call apply bind
call
call
是一个方法,它的作用是改变函数的this
指向。第一个参数是用于替换this
的值,后续的参数是传递给函数的参数。例如:
1 | function showName(age, job) { |
call
会立即执行函数。
apply
apply
方法的作用和call
类似,都是用于改变函数的this
指向,不过apply
接收的参数是一个数组(或类数组对象),第一个参数同样是用于替换this
的值,第二个参数是传递给函数的参数数组。例如:
1 | let person = { name: 'Tom' }; |
和call
一样,apply
也会立即执行函数。
bind
bind
方法也是用于改变函数的this
指向,它的使用方法和call
类似,第一个参数是用于替换this
的值,后续的参数是传递给函数的参数。不过,不同于call
和apply
,bind
不会立即执行函数,而是返回一个新的函数。例如:
1 | let person = { name: 'Tom' }; |
总结:
call
和apply
的主要作用都是改变函数的this
指向,并立即执行函数。它们的区别主要在于参数的传递方式,call
是将参数依次传入,而apply
则是以数组形式传入参数。bind
方法也可以改变函数的this
指向,但不同的是,它会返回一个新的函数,可以在需要的时候再去调用这个新函数。
2. 事件等级
DOM 0 级事件
DOM 0 级事件又称为原始事件模型,其事件绑定方法非常简单,直接在 HTML 元素上通过事件属性(如 onclick、onload、onmouseover 等)绑定 JavaScript 函数,或者在 JavaScript 代码中通过 JavaScript 对象的事件属性进行绑定。
例如:
1 | // 通过 HTML 属性直接指定 |
注意,在使用 DOM 0 级事件时,同一个事件只能绑定一个处理函数,多次设置会覆盖之前的处理器。
DOM 2 级事件
DOM 2 级事件提供了更多的事件类型和更丰富的事件处理方式。事件绑定主要使用 addEventListener
和 removeEventListener
方法。
addEventListener
方法接受三个参数:事件名称,事件处理函数,和一个布尔值(可以指定事件处理器在捕获阶段或者冒泡阶段执行)。
例如:
1 | let button = document.getElementById('myButton'); |
不同于 DOM 0 级事件,DOM 2 级事件可以为同一个事件绑定多个处理函数,这些函数将按照绑定顺序依次执行。同时,可以通过 removeEventListener
移除绑定的事件处理函数。
DOM 3 级事件
DOM 3 级事件在 DOM 2 的基础上,引入了更多的事件类型,例如键盘事件、鼠标滚轮事件等。有以下常见的几类:
- 鼠标事件:click、dblclick、mousedown、mouseup、mousemove、mouseover、mouseout、mouseenter、mouseleave 等。
- 键盘事件:keydown、keyup、keypress。
- 表单事件:focus、blur、change、submit。
- 窗口事件:scroll、resize、load、unload。
3. JavaScript 继承
在 JavaScript 中,有多种方法实现对象的继承,其中包括原型链继承、组合继承、原型式继承、寄生式继承、寄生组合式继承,以及 ES6 中引入的基于 class
关键字的继承。
原型链继承
JavaScript 的每个对象都有一个指向它的原型(prototype)的链接。当试图访问一个对象的属性时,如果对象内部没有这个属性,那么 JavaScript 就会去对象的原型上找这个属性,这个过程叫做原型链查找。
1 | function Parent() { |
缺点:父类的引用属性会被所有实例共享,一个实例修改了父类的引用属性,其他实例的这个属性也会被修改。
组合继承(经典继承)
组合继承是 JavaScript 最常用的继承模式。思路是使用原型链实现对原型属性和方法的继承,通过构造函数来实现对实例属性的继承。
1 | function Parent(value) { |
原型式继承
原型式继承的思路是基于已经存在的对象创建新对象,同时还不必因此创建自定义类型。
1 | function object(o) { |
ES5 通过新增 Object.create() 方法规范化了原型式继承。
1 | var parent = { |
寄生式继承
寄生式继承的思路是创建一个用于封装继承过程的函数,该函数在内部以某种方式来增强对象,最后返回对象。
1 | function createAnother(original) { |
寄生组合式继承
寄生组合式继承是将寄生式继承和组合继承进行组合应用的方法。这种类型的继承效率较高,是 JavaScript 中最理想的继承范式。
1 | function Parent(value) { |
ES6 的 class 继承
ES6 中,可以使用 class
关键字来定义类,并通过 extends
和 super
关键字来实现类的继承。
1 | class Parent { |
class
继承的背后其实是原型链继承和构造函数继承的组合,是语法糖。
4. 事件循环
JavaScript 是单线程语言,为了实现执行异步操作,所以引入了事件循环(Event Loop)。它是一个持续运行的过程,可以理解为一个实际的循环,它在等待事件发生时继续运行。
宏任务和微任务
JavaScript 的任务可以分为宏任务和微任务
宏任务:可以理解为需要在一次事件循环中全部执行完毕的任务,例如
setTimeout
、setInterval
、setImmediate
(Node.js 环境)、I/O
、UI rendering
等。微任务:可以理解为需要在当前任务执行结束后立即执行的任务,例如
Promise
、process.nextTick
(Node.js 环境)、MutationObserver
等。
事件循环过程
执行同步代码,这属于一个宏任务。
执行完所有同步代码后,执行下一个宏任务前,在下一个宏任务开始前,会执行所有的微任务。
当所有微任务执行完毕后,有可能需要进行 UI 渲染。
然后继续下一个宏任务,执行对应的任务队列。
循环上述步骤。
经典题目
1 | console.log('1'); |
注意,await 后面的东西相当于new Promise(function(resolve) {console.log('7');resolve();
里面的console.log('7')
5. 模块化
AMD (Asynchronous Module Definition)
AMD 是 “Asynchronous Module Definition” 的缩写,意思就是 “异步模块定义”。它采用异步方式加载模块,模块的加载不影响后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。AMD 的代表实现是 RequireJS。
1 | // 定义一个模块 |
CMD (Common Module Definition)
CMD 是 “Common Module Definition” 的缩写,也就是 “通用模块定义”。CMD 采用的是异步加载模块,允许模块和模块之间有依赖关系,也支持就近依赖,只有在用到某个模块的时候再去加载那个模块。CMD 的代表实现是 SeaJS。
1 | define(function (require, exports, module) { |
CommonJS
CommonJS 是 Node.js 模块系统的基础,它用于服务器。每个文件是一个模块,通过 require
来加载模块,通过 exports
或 module.exports
来导出模块。CommonJS 使用同步加载模块的方式。
1 | var dep1 = require('dep1'); |
ES6 Modules
ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,旨在成为浏览器和服务器通用的模块解决方案。模块不是对象,import
命令会被 JavaScript 引擎静态分析,生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块去取值。
1 | import { dep1, dep2 } from 'module-name'; |
6. 迭代器和生成器
迭代器 (Iterator)
迭代器是一种特殊对象,它包含一个叫做 next 的方法。这个方法返回一个结果对象,这个对象有两个属性:
- value:下一个值
- done:布尔类型,如果没有更多的数据则为 true,否则为 false。
迭代器的简单示例:
1 | let iterator = { |
生成器(Generator)和 yield 关键字
生成器是一种可以返回多个连续值的函数。换句话说,一个生成器就像一个工厂,制造一系列的值。生成器函数通过 * 符号来定义,并且可以通过 yield 关键字来产生一个值。
当一个生成器函数被调用时,它返回一个特殊类型的迭代器,称为生成器对象。这个对象也包含一个 next 方法,但是可以在生成器函数内部控制这个方法的行为。
生成器的简单示例:
1 | // 例子1 |
每次调用生成器对象的 next 方法,就会执行生成器函数直到遇到一个 yield 语句。然后,yield 的值就是 next 方法返回的值。当没有更多的 yield 语句时,done 属性的值就会变成 true。
next()
函数还可以接收一个参数,这个参数可以作为 yield
语句的结果返回给生成器内部。这个特性可以使得生成器和外部环境进行双向的数据交换。比如例子 2 里调用 iter.next('Hello, generator!')
,传入的参数 'Hello, generator!'
会作为上一次 yield
语句的结果,赋值给 dataIn
变量。然后生成器恢复执行,直到遇到下一个 yield
语句,或者结束。