前言
在《javascript 之执行环境-08》文中说到,当JavaScript代码执行一段可执行代码时,会创建对应的执行上下文(execution context)。对于每个执行上下文,都有三个重要属性:
- 变量对象(Variable object,VO)
- 作用域链(Scope chain)
- this
JavaScript
中的this
跟其他语言有些不一样,比如Java .net
语言中的this
是在代码的执行阶段是不可变的,而JavaScript的this
是在调用阶段进行绑定。也因为这一性质给了this
很大的灵活性,即当函数在不同的调用方式下都可能会导致this的值不同;
定义
this 对象是在运行时基于函数的执行环境绑定的,跟函数的调用位置有关而不是声明的位置;可以理解为this是在函数调用阶段绑定,也就是执行上下文创建的阶段进行赋值,保存在变量对象中;
四种绑定规则
new 构造函数绑定,this指向新创建的对象
1 function createPerson(){ 2 return new person(); 3 } 4 function person() { 5 this.name = "Joel"; 6 this.say=function(){ 7 console.log(p) 8 console.log('hello' + this.name) 9 } 10 } 11 var p = new person(); 12 p.say(); 13 console.log(p.name);//Joel 14 console.log('开始') 15 var t=createPerson(); 16 t.say();
不管是直接new 还是通过function createPerson 函数返回的对象,this 都是指向了新创建出来的对象;
显示绑定,this指向传进去的对象
1 //call() apply() bind() 显示绑定 2 window.color = "red"; 3 var Obj = { color: "blue" }; 4 function sayColor() { 5 console.log(this.color); 6 } 7 sayColor();// red this window 8 sayColor.call(this);//red 9 sayColor.call(window);//red 10 sayColor.call(Obj);// blue 把this指针改为对象Obj 11 sayColor.apply(Obj);//blue 把this指针改为对象Obj 12 sayColor.bind(Obj)();//blue 把this指针改为对象Obj
如果你把null/undefined作为this的绑定对象传入call/apply/bind,这些值在调用时会被忽略,实际用的是默认的绑定规则;
1 function foo() { 2 console.log(this.a) 3 } 4 var a=2; 5 foo.call(null);//2 6 foo.call(undefined);//2
隐士绑定
以对象的方法形式调用,this指向当前这个对象
1 function foo(){ 2 console.log(this)//{a: 2, name: "Joel", foo: ƒ} 3 console.log(this.a)//2 4 } 5 var obj={ 6 a:2, 7 name:'Joel', 8 foo:foo 9 }; 10 obj.foo();
这时this指向当前obj对象,但是如果换种写法会造成this 丢失问题。
1 function foo(){ 2 console.log(this)//{a: 2, name: "Joel", foo: ƒ} 3 console.log(this.a)//2 4 } 5 var obj={ 6 a:2, 7 name:'Joel', 8 foo:foo 9 }; 10 obj.foo(); 11 //this 丢失的问题 12 var t= obj.foo; 13 t(); //window undefined
变量t此时保存的是函数的引用跟obj已经没有关系,所以此时this指向window。
默认绑定 严格模式下this 绑定到undefined,否则绑定到全局对象 window
1 function foo(){ 2 console.log(this)//window 3 console.log(this.a)//Joel 4 } 5 var a='Joel'; 6 foo(); 7 8 //严格模式 9 function fo(){ 10 'use strict' //严格模式 11 console.log(this)//undefined 12 console.log(this.b)//报错 Cannot read property 'b' of undefined 13 } 14 var b='Joel'; 15 fo();
以上是基本的this绑定规则,其中new、显示绑定很容易判断,其中比较容易错的是容易把默认绑定误认为是隐士绑定 如匿名函数、闭包、函数当做参数等;
独立调用:this 指向window
1 var name='Joel',age=12; 2 function say(){ 3 function say1(){ 4 console.log(this.name);//window 5 function say2(){ 6 name+='-l' 7 console.log(this.name);//window 8 } 9 say2() 10 } 11 say1(); 12 } 13 say(); 14 15 //匿名函数 16 (function(){ 17 console.log(this.name) 18 })()
function 当做参数传递其实跟独立调用一样的原理
1 function foo() { 2 console.log(this)//window 3 console.log(this.a)//oops global 4 } 5 function doFoo(fn) { 6 console.log(this);//window 7 fn();//类似与 foo() 8 } 9 var obj = { 10 a: 2, 11 foo: foo 12 } 13 var a = 'oops global'; 14 doFoo(obj.foo);
同理setTimeout 也是一样
1 var obj = { 2 a: 2, 3 foo: function() { 4 console.log(this); 5 }, 6 foo2: function() { 7 console.log(this); //this 指向 obj 8 setTimeout(this.foo, 1000); // this 指向 window 9 } 10 } 11 var a = 'oops global'; 12 obj.foo2();
闭包中的this
1 var name = "Joel"; 2 var obj = { 3 name: "My object", 4 getName: function() { 5 // var that = this; // 将getNameFunc()的this保存在that变量中 6 return function() { 7 return this.name; 8 }; 9 } 10 } 11 console.log(obj.getName()()); // "Joel"
这里的虽然是对象的方法形式调用obj.getName(),在getName中的this是指向obj,但是返回的匿名函数中的this 为什么是window呢?
把最后的一句拆成两个步骤执行:
1 var t=obj.getName(); 2 t();
是不是有点像在独立调用呢?如果需要访问obj中的name,只需要把this对象缓存起来,在匿名函数中访问即可,把var that=this;去掉注释即可;
总结
- this 是变量对象的一个属性,是在调用时被绑定的,跟函数的调用位置有关而不是声明的位置;
- 找到调用位置,根据绑定规则来分析this 绑定;
- 默认绑定严格模式下this 绑定到undefined,否则绑定到全局对象 window;
思考题
1 var length = 5; 2 var obj = { 3 foo: function (fn) { 4 console.log(this.length); // this => obj 5 fn(); // this => window 6 arguments[0](); // this => arguments 7 var bar = arguments[0]; 8 bar(); // this => window 9 }, 10 length: 10 11 } 12 var fn = function () { 13 console.log(this.length); 14 } 15 obj.foo(fn); 16 //10, 5, 1, 5