JS 深入浅出This指向(精简)
从原理出发
首先我们围绕耳熟能详的“this
始终指向它的调用者”开始。这句结论虽然没有什么问题,但是说得过于笼统,还是得深入到背后的执行原理才能举一反三解决问题。
举个简单的例子:
var obj = {
num: 2;
foo: function () { console.log(this.num) }
};
var foo = obj.foo;
var num = 3;
obj.foo();//2
foo();//3
①obj.foo();
是对象obj
调用自身的属性(方法)foo
,this
指向调用者obj
。
②由var foo = obj.foo;
获取函数,并用foo();
在全局作用域中调用函数,所以this
指向浏览器全局对象Window
。
内存数据结构的概述
JS之所以设计this
,是因为其内存的数据结构的特征。
var obj = { foo: 5 };
①上面的对象赋值给变量,其实质是JS引擎现在内存中生成{foo: 5}
,然后再把该对象的内存地址赋值给变量obj
。也就是说onj.foo
实质上是先从obj
获取对象的内存地址然后再从地址读取原始对象,最后返回对象属性foo
的值。
②原始对象以词典结构保存,一个属性名对应一个属性描述对象。如下图,foo
属性的描述对象就包含4个描述属性,而最重要的值保存在描述对象[[value]]
中。
当foo
属性值为函数时,JS引擎同样会先将原始函数保存在内存中,然后把该函数的内存地址存放于foo
属性的描述对象中的[[value]]
里面。
从原理出发章节参考于JavaScript 的 this 原理——阮一峰的网络日志
为何造就this
我们需要JS在函数体内部可以引用当前环境的其他内部变量,就像这样:
var f = function () {-
console.log(this.x)//内部引用当前调用者变量x
};
var x = 1;//实质给全局对象Window添加属性 x 并赋值 1
var obj = {
x: 2,//调用者内部变量
f: f
}
f();//结果为1,执行于全局环境,所以 this.x 指向 Window.x
boj.f();//结果为2,执行于 obj 内部环境,所以 this.x 指向 obj.x
这就说明了造就this
的目的就是为函数内部的语句指定当前运行环境,而当前运行环境不一定就是该函数内部环境哈。
this
的扩展延伸
构造函数与this
function name () {
this.fne = "yulin"
};
var a = new name();
console.log(a.fne);//yulin
关于这个构造函数这里先笼统提一些,new
关键字会创建一个空对象name { }
,并且会将函数name()
的保留在[[Prototype]]
原型中而且会执行该函数并返回执行结果(这里有几种情况下文会提到),最后整个对象name { }
赋值给变量a
,a
即是对象实例。所以this.fne
指向a.fne
。
this
与return
的问题
上文说到构造函数执行的返回结果的有几种情况:
- 返回对象时,
this
会指向返回的对象,null
除外,否则指向起始调用者。
function name () {
this.fne = "yulin";
return {}
};
var a = new name();
console.log(a.fne);//undefined,指向 {}
function name () {
this.fne = "yulin";
return function () {}
};
var a = new name();
console.log(a.fne);//undefined,指向 function () {}
function name () {
this.fne = "yulin";
return null
};
var a = new name();
console.log(a.fne);//yulin,指向 null,null是特殊的对象不会更改 this
function name () {
this.fne = "yulin";
return undefined
};
var a = new name();
console.log(a.fne);//yulin,指向 name {}
更灵活的this
指向方法
- 当我们需要在a对象中调用b对象的方法时,我们就需要用到
call()
方法指定目的运行环境。
const a = {
name: 'yulin',
fn: function (e, q) {
console.log(this.name);
console.log(e + q);
}
};
const b = {
name: 'yhh'
}
var x = a.fn
x.call(b, 2, 3);//yhh 5
apply()
方法与call()
类型,但传入apply
的第二个参数必须是数组,就像[1, 2, 3]
,[a , b, c]
以及数组变量。
const a = {
name: 'yulin',
fn: function (e, q) {
console.log(this.name);
console.log(e + q);
}
};
const b = {
name: 'yhh'
}
var x = a.fn
x.apply(b, [2, 3]);//yhh 5
/*
以下代码效果一样
let arr = [2, 3];
x.apply(b, arr);
*/
请注意:a.call(null)
和a.apply(null)
的this
指向都是Window
。
3. bind()
与call()
, apply()
完全不同,bind()
改变this
指向的同时还返回被调用的属性,方法。
const a = {
name: 'yulin',
fn: function (e, q) {
console.log(this.name);
console.log(e + q);
}
};
const b = {
name: 'yhh'
}
var x = a.fn
x.bind(b);
/*? (e, q) {
console.log(this.name);
console.log(e + q);
}*/
let y = x.bind(b);
y(2, 3);//yhh 5