使用 JavaScript 开发的时候,很多开发者多多少少会被 this 的指向搞蒙圈,但是实际上,关于 this 的指向,记住最核心的一句话:哪个对象调用函数,函数里面的 this 指向哪个对象。
this的几种模式:
函数调用下,this 指向 window ,调用方法没有明确对象的时候,this 指向 window,如 setTimeout、匿名函数等;
构造函数调用模式下,this 指向被构造的对象;
apply,call,bind 调用模式下,this 指向第一个参数;
箭头函数,在声明的时候绑定this,而非取决于调用位置;
严格模式下,如果 this 没有被执行环境(execution context)定义,那 this是 为undefined;
下面我们针对这几种情况,举例并说明原理:
1. 方法调用模式
// 声明位置 var test = function(){ console.log(this.x) } var x = "2"; var obj = { x:"1", fn:test, } // 调用位置 obj.fn(); // 1 test(); // 2
以上代码,可以看到,this 指向调用它所在方法的对象,test 方法在 obj 对象下,所以 this 指向 obj,test 在window 对象下,所以 this 指向 window。也可以看出来:this和声明位置无关,和调用位置有关。
但是下面这个情况得注意
let obj1={ a:222 }; let obj2={ a:111, fn:function(){ console.log(this.a); } } obj1.fn = obj2.fn; obj1.fn(); // 222
这个不难理解,虽然 obj1.fn 是从 obj2.fn 赋值而来,但是调用函数的是obj1,所以 this 指向 obj1。
2. 函数调用模式
var a = 1; function fn1(){ console.log(this.a); // 1 } fn1(); window.b = 2; function fn2(){ console.log(this.b); // 2 } fn2(); //可以理解为 window.fn();
匿名函数,setTimeout:
(function(){ console.log(this); // window })(); setTimeout(() => { console.log(this); // window }, 0); setTimeout(function(){ console.log(this); // window }, 0);
3. 构造函数模式
var flag = undefined; function Fn(){ flag = this; } var obj = new Fn(); console.log(flag === obj); // true
这个 this 指向 obj,内部原理还是用 apply 把 this 指向obj的,回忆下JavaScript中 new 一个对象过程详解。
4. call、apply、bind
call 和 apply 的作用,完全一样,唯一的区别:参数;
call 接收的参数不固定,第一个参数是函数体内 this 的指向,第二个参数以下是依次传入的参数。
apply接收两个参数,第一个参数也是函数体内 this 的指向。第二个参数是一个集合对象(数组或者类数组)
var obj = { name:'111', getName:function(){ console.log(this.name) } }; var otherObj = { name:'222', }; var name = '333'; obj.getName(); // 111 obj.getName.call(); // 333 obj.getName.call(otherObj); // 222 obj.getName.apply(); // 333 obj.getName.apply(otherObj); // 222 obj.getName.bind(this)(); // 333 obj.getName.bind(otherObj)();// 222
5. 箭头函数
关于 ES6 中的箭头函数,官方的解释是:
箭头函数里面的 this 是上下文( context ), 外部作用域的 this 就是箭头函数内的 this。
判断箭头函数的 this:
技巧:它的外层没有函数,this 是 window;外层有函数,看外层函数的 this 是谁,它的 this 就是谁。
外层函数可能是常规函数可能是箭头函数,判断外层的 this 要根据函数种类用不同方法:
外层函数是常规函数就看外层函数是被谁调用的;
外层是箭头函数就根据刚才说的技巧来判断;
let obj={ a:222, fn:function(){ setTimeout(()=>{console.log(this.a)}); } }; obj.fn(); // 222
var name = 'window'; var A = { name: 'A', sayHello: () => { console.log(this.name) } } A.sayHello(); // 输出的是window,根据刚才讲的规则就可以判断 // 那如何改造成永远绑定A呢: var name = 'window'; var A = { name: 'A', sayHello: function(){ var s = () => console.log(this.name) return s//返回箭头函数s } } var sayHello = A.sayHello(); sayHello();// 输出A
- call() 、apply() 、 bind() 方法对于箭头函数来说只是传入参数,对它的 this 毫无影响;
- 考虑到 this 是词法层面上的,严格模式中与 this 相关的规则都将被忽略(忽略是否在严格模式下的影响);
var globalObject = this; var foo = (() => this); console.log(foo() === globalObject); // true var obj = {foo: foo}; console.log(foo.call(obj) === globalObject); // true foo = foo.bind(obj); console.log(foo() === globalObject); // true
6. 严格模式
非严格模式下,this 默认指向全局对象 window;
// 非严格模式 function f1(){ return this; } console.log(f1() === window); // true
严格模式下, this 为undefined;
// 严格模式 "use strict"; var fn2 = function(){ return this } console.log(fn2() == undefined); // true