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