|
2 | 2 | title: 'JavaScript 相关'
|
3 | 3 | icon: 'javascript'
|
4 | 4 | order: 1
|
| 5 | +headerDepth: 1 |
5 | 6 | ---
|
6 | 7 |
|
7 |
| -## 讲讲 apply、call、bind 的区别 |
| 8 | +## apply、call、bind 的区别是什么? |
8 | 9 |
|
9 |
| -- call 和 apply 都是立即改变,bind 则是返回一个函数等待下一次调用 |
10 |
| -- call 和 bind 的参数形式相同,apply 的形式是数组 |
11 |
| -- call 的性能比 apply 高 |
| 10 | +::: details 答案 |
12 | 11 |
|
13 |
| -## 数组方法中哪些会改变原数组?哪些不改变原数组? |
| 12 | +`call`、`apply`、`bind`作用是改变函数执行时的上下文,简而言之就是改变函数运行时的 `this` 指向 |
| 13 | + |
| 14 | +那什么情况下需要改变`this`的指向呢?下面举个例子 |
| 15 | + |
| 16 | +```js |
| 17 | +var name = 'lucy'; |
| 18 | +var obj = { |
| 19 | + name: 'martin', |
| 20 | + say: function () { |
| 21 | + console.log(this.name); |
| 22 | + } |
| 23 | +}; |
| 24 | +obj.say(); // martin,this 指向 obj 对象 |
| 25 | +setTimeout(obj.say, 0); // lucy,this 指向 window 对象 |
| 26 | +``` |
| 27 | + |
| 28 | +从上面可以看到,正常情况`say`方法输出`martin` |
| 29 | + |
| 30 | +但是我们把`say`放在`setTimeout`方法中,在定时器中是作为回调函数来执行的,因此回到主栈执行时是在全局执行上下文的环境中执行的,这时候`this`指向`window`,所以输出`lucy` |
| 31 | + |
| 32 | +我们实际需要的是`this`指向`obj`对象,这时候就需要该改变`this`指向了 |
| 33 | + |
| 34 | +```js |
| 35 | +setTimeout(obj.say.bind(obj), 0); // martin,this指向obj对象 |
| 36 | +``` |
| 37 | + |
| 38 | +### apply |
| 39 | + |
| 40 | +`apply`接受两个参数,第一个参数是`this`的指向,第二个参数是函数接受的参数,以数组的形式传入 |
| 41 | + |
| 42 | +改变`this`指向后原函数会立即执行,且此方法只是临时改变`this`指向一次 |
| 43 | + |
| 44 | +```js |
| 45 | +function fn(...args) { |
| 46 | + console.log(this, args); |
| 47 | +} |
| 48 | +let obj = { |
| 49 | + myname: '张三' |
| 50 | +}; |
| 51 | + |
| 52 | +fn.apply(obj, [1, 2]); // this会变成传入的obj,传入的参数必须是一个数组; |
| 53 | +fn(1, 2); // this指向window |
| 54 | +``` |
| 55 | + |
| 56 | +当第一个参数为`null`、`undefined`的时候,默认指向`window`(在浏览器中) |
| 57 | + |
| 58 | +```js |
| 59 | +fn.apply(null, [1, 2]); // this指向window |
| 60 | +fn.apply(undefined, [1, 2]); // this指向window |
| 61 | +``` |
| 62 | + |
| 63 | +### call |
| 64 | + |
| 65 | +`call`方法的第一个参数也是`this`的指向,后面传入的是一个参数列表 |
| 66 | + |
| 67 | +跟`apply`一样,改变`this`指向后原函数会立即执行,且此方法只是临时改变`this`指向一次 |
| 68 | + |
| 69 | +```js |
| 70 | +function fn(...args) { |
| 71 | + console.log(this, args); |
| 72 | +} |
| 73 | +let obj = { |
| 74 | + myname: '张三' |
| 75 | +}; |
| 76 | + |
| 77 | +fn.call(obj, 1, 2); // this会变成传入的obj,传入的参数必须是一个数组; |
| 78 | +fn(1, 2); // this指向window |
| 79 | +``` |
| 80 | + |
| 81 | +同样的,当第一个参数为`null`、`undefined`的时候,默认指向`window`(在浏览器中) |
| 82 | + |
| 83 | +```js |
| 84 | +fn.call(null, [1, 2]); // this指向window |
| 85 | +fn.call(undefined, [1, 2]); // this指向window |
| 86 | +``` |
| 87 | + |
| 88 | +### bind |
| 89 | + |
| 90 | +`bind` 方法和 `call` 很相似,第一参数也是 `this` 的指向,后面传入的也是一个参数列表(但是这个参数列表可以分多次传入) |
| 91 | + |
| 92 | +改变 `this` 指向后不会立即执行,而是返回一个永久改变 `this` 指向的函数 |
| 93 | + |
| 94 | +```js |
| 95 | +function fn(...args) { |
| 96 | + console.log(this, args); |
| 97 | +} |
| 98 | +let obj = { |
| 99 | + myname: '张三' |
| 100 | +}; |
| 101 | + |
| 102 | +const bindFn = fn.bind(obj); // this 也会变成传入的 obj ,bind 不是立即执行需要执行一次 |
| 103 | +bindFn(1, 2); // this 指向 obj |
| 104 | +fn(1, 2); // this 指向 window |
| 105 | +``` |
| 106 | + |
| 107 | +从上面可以看到,`apply`、`call`、`bind`三者的区别在于: |
| 108 | + |
| 109 | +- 三者都可以改变函数的 `this` 对象指向 |
| 110 | +- 三者第一个参数都是 `this` 要指向的对象,如果如果没有这个参数或参数为 `undefined` 或 `null`,则默认指向全局 `window` |
| 111 | +- 三者都可以传参,但是 `apply` 是数组,而 `call` 是参数列表,且 `apply` 和 `call` 是一次性传入参数,而 `bind` 可以分为多次传入 |
| 112 | +- `bind` 返回绑定 `this` 之后的函数,`apply`、`call` 则是立即执行 |
| 113 | + |
| 114 | +::: |
| 115 | + |
| 116 | +## 如何实现一个 `bind` ? |
| 117 | + |
| 118 | +::: details 答案 |
| 119 | + |
| 120 | +实现`bind`的步骤,我们可以分解成为三部分: |
| 121 | + |
| 122 | +- 修改`this`指向 |
| 123 | +- 兼容`new`关键字 |
| 124 | +- 动态传递参数 |
| 125 | + |
| 126 | +```js |
| 127 | +// 方式一:只在 bind 中传递函数参数 |
| 128 | +fn.bind(obj, 1, 2)(); |
| 129 | + |
| 130 | +// 方式二:在 bind 中传递函数参数,也在返回函数中传递参数 |
| 131 | +fn.bind(obj, 1)(2); |
| 132 | +``` |
| 133 | + |
| 134 | +整体实现代码如下: |
| 135 | + |
| 136 | +```js |
| 137 | +Function.prototype.myBind = function (context) { |
| 138 | + // 判断调用对象是否为函数 |
| 139 | + if (typeof this !== 'function') { |
| 140 | + throw new TypeError('Error'); |
| 141 | + } |
| 142 | + |
| 143 | + // 获取参数 |
| 144 | + const args = [...arguments].slice(1); |
| 145 | + const fn = this; |
| 146 | + |
| 147 | + return function Fn() { |
| 148 | + // 根据调用方式,传入不同绑定值 |
| 149 | + return fn.apply( |
| 150 | + this instanceof Fn ? new fn(...arguments) : context, |
| 151 | + args.concat(...arguments) |
| 152 | + ); |
| 153 | + }; |
| 154 | +}; |
| 155 | +``` |
| 156 | + |
| 157 | +::: |
| 158 | + |
| 159 | +## 数组方法中哪些会改变原数组,哪些不会? |
| 160 | + |
| 161 | +::: details 答案 |
14 | 162 |
|
15 | 163 | `let arr = ['a', 'b', 'c', 'd'];`
|
16 | 164 |
|
17 |
| -### 改变原数组的 |
| 165 | +### 改变原数组的: |
18 | 166 |
|
19 | 167 | - `shift`:将第一个元素删除并且返回删除元素,空即为 `undefined`
|
20 | 168 |
|
@@ -109,7 +257,7 @@ a = arr.map((i) => (i.m = 3)); // 注意返回值的差异
|
109 | 257 | console.log(a); // [3, 3]
|
110 | 258 | ```
|
111 | 259 |
|
112 |
| -### 不改变原数组的 |
| 260 | +### 不改变原数组的: |
113 | 261 |
|
114 | 262 | - `concat`:用于合并两个或多个数组,不会更改原数组,而是返回一个新数组
|
115 | 263 |
|
@@ -169,7 +317,11 @@ console.log(arr); // ['a', 'b', 'c', 'd']
|
169 | 317 |
|
170 | 318 | - `every`、`flat`、 `keys`、 `toString`、 `entries` 等
|
171 | 319 |
|
172 |
| -## 使用 `new` 时发生了什么 |
| 320 | +::: |
| 321 | + |
| 322 | +## 使用 `new` 时发生了什么? |
| 323 | + |
| 324 | +::: details 答案 |
173 | 325 |
|
174 | 326 | 当你使用 `new` 关键字调用一个构造函数时,它将:
|
175 | 327 |
|
@@ -204,6 +356,8 @@ const salva = new Person('Salva');
|
204 | 356 |
|
205 | 357 | - [构造函数介绍 -- mozilla](https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/Objects/Basics#%E6%9E%84%E9%80%A0%E5%87%BD%E6%95%B0%E4%BB%8B%E7%BB%8D)
|
206 | 358 |
|
| 359 | +::: |
| 360 | + |
207 | 361 | ## 讲一讲原型链
|
208 | 362 |
|
209 |
| -## 手写一个 Promise |
| 363 | +## 如何实现一个 `Promise` ? |
0 commit comments