1.this指向
- 全局环境中的this -> 指向
window
对象
- 普通函数中的this -> 严格模式下指向
undefined
,非严格模式下指向window
对象
- 构造函数中的this -> 指向
new
出来的对象
call、apply、bind
调用 -> 指向这三个方法的第一个参数,如果参数为null
或者undefined
,在非严格模式下指向window
对象
- 箭头函数中的this -> 取决于箭头函数外的this指向
在非严格模式下,this指向不能是undefined
或null
,如果得出this将指向undefined
或者null
,那么this会指向window
对象。
在浏览器环境下是window
对象,在Node环境下是global
。
简单一句话概括就是:谁调用,this就指向谁。
2.优先级
当有多种情况决定this指向时,优先级依次为:
- 箭头函数
- new
- bind
- apply和call
- 对象调用方法
- 直接调用函数
- 全局环境
例如:
1 2 3 4
| let func = () => { console.log(this); } func.bind({})();
|
3.apply、call和bind
这三个方法都会改变函数中的this指向,那他们有什么区别呢?以下方代码为例:
1 2 3 4
| function test(x, y){ console.log(this, x, y); } test();
|
(1) call
call
方法接收多个参数,第一个参数为要改变的this指向,后边参数为函数自身的参数。
1 2 3 4 5
| let temp = { name: "小明" }; function test(x, y){ console.log(this, x, y); } test.call(temp,1,2);
|
接下来我们尝试自己去实现一下call
方法。
我们思考一下,怎么做才能让函数中的this指向一个对象?如果这个函数是对象体内的方法,那通过对象调用这个方法,函数中的this是不是就指向调用的对象。然后在调用完函数后,将这个方法删除掉,对象内容不变:
1 2 3 4 5 6 7
| let temp = { name: "小明" }; function test(x, y){ console.log(this, x, y); } temp.fn = test; temp.fn(1, 2); delete temp.fn;
|
按照这个思路,我们试着自己去写一个call
方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| Function.prototype._call = function (context) { context = context || window; const fn = Symbol(); context[fn] = this; const res = context[fn](...[...arguments].slice(1)); delete context[fn]; return res; }; let temp = { name: "小明" }; function test(x, y){ console.log(this, x, y); } test._call(temp, 1, 2);
|
(2) apply
apply
方法接收两个参数,第一个参数为要改变的this指向,第二个参数为函数自身的参数,以数组的形式传入。apply
与call
方法不同的地方就是传参的形式。
1 2 3 4 5
| let temp = { name: "小明" }; function test(x, y){ console.log(this, x, y); } test.apply(temp,[1,2]);
|
接下来我们尝试自己去实现一下apply
方法。
apply
方法和call
方法一样,只不过是传入参数的方式不同而已:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| Function.prototype._apply = function (context) { context = context || window; const fn = Symbol(); context[fn] = this; let res = null; if (arguments[1]) { res = context[fn](...arguments[1]); } else { res = context[fn](); } delete context[fn]; return res; }; let temp = { name: "小明" }; function test(x, y){ console.log(this, x, y); } test._apply(temp, [1, 2]);
|
(3) bind
bind
方法接收多个参数,第一个参数是要改变的this指向,后边参数为函数自身的参数。它与上边两个方法不同的是,它不会立刻执行函数,而是返回一个新函数。
1 2 3 4 5 6 7 8 9 10 11 12 13
| let temp = { name: "小明" }; function test(x, y){ console.log(this, x, y); }
let fn = test.bind(temp, 1, 2); fn();
test.bind(temp, 1, 2)();
test.bind(temp)(1, 2); test.bind(temp, 1)(2);
|
接下来我们尝试自己去实现一下bind
方法。
bind
返回的是一个新函数,所以实现起来和上面两个有所不同:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| Function.prototype._bind = function (context) { const args = [...arguments].slice(1); const fn = this; return function() { return fn.apply( context, args.concat(...arguments) ); }; }; let temp = { name: "小明" }; function test(x, y) { console.log(this, x, y); } test._bind(temp, 1, 2)(); test._bind(temp, 1)(2);
|
但是这样还有个瑕疵,在MDN中有这么一句话:
bind()中的第一个参数:调用绑定函数时作为this参数传递给目标函数的值。 如果使用new运算符构造绑定函数,则忽略该值。
这句话中的绑定函数指bind
返回的函数,那这句话是什么意思呢?由于bind
方法返回的是一个函数,那么这个函数可以作为构造函数使用,当这个函数作为构造函数使用时,原来函数中的this指向的应该是这个构造函数创建的实例对象,而不是bind
绑定的对象。
我们还是看回这个例子:
1 2 3 4 5 6
| let temp = { name: "小明" }; function test(x, y) { console.log(this, x, y); }
let obj = new (test.bind(temp, 1, 2))();
|
如果用我们上面自己的写的bind
方法,当返回函数作为构造函数时,this指向还是temp对象,所以要进一步修改:
1 2 3 4 5 6 7 8 9 10
| Function.prototype._bind = function (context) { const args = [...arguments].slice(1); const fn = this; return function Fn() { return fn.apply( this instanceof Fn ? this : context, args.concat(...arguments) ); }; };
|
参考:
https://juejin.cn/post/6844903746984476686
https://juejin.cn/post/6946021671656488991