不知道看了多少篇关于this的文章,很难理解,每次看到的文基本都是说明了this
的几个不一样环境的指向。而后像记住历史同样,记在了脑子里,但这种记忆随着时间的变化会愈来愈分辨不清。javascript
主要么,也是我没有真正深刻的去了解this
。真的去深刻了,才发现this
涉及的内容真的好多。html
this
原理在《高程3》中的p182的第9行写到:在全局函数中,this
等于window
;当函数做为某个对象的方法调用时,this
等于那个对象;匿名函数的执行环境具备全局性,因此this
等于window
。前端
这个概念,应该是一个前端必备的知识点吧?那么问题来了,为何会有这样一个断定呢?java
先了解一个概念:浏览器
执行上下文:也叫一个执行环境,有 全局执行环境 和 函数执行环境 和 eval
。数据结构
每一个执行环境中包含这三部分:变量对象/活动对象,做用域链,this
的值app
下面这段代码你们确定都不陌生函数
var obj = {
name: 'a',
say: function(){
console.log(this.name)
}
}
var say = obj.say;
obj.say(); //行1 a
say(); //行2 undefined
复制代码
以上那几行代码,其实包含了两个我曾经一度没有深刻理解的内容:ui
this
是在函数运行时基于的函数的执行上下文绑定的。这个时候须要了解一下 执行上下文的建立过程
- 对象的值都是存入内存的,而
this
跟内存里面的数据结构有关系,了解一下阮大大的 javascript 的 this 原理
我这里直接进入对象中的函数的声明与调用,以上文中的obj
为例。this
在建立执行上下文中,变量的声明的步骤依次为:建立、初始化、赋值。对象是一种特殊的变量,因此它也存在这3个步骤。
在初始化以后赋值以前,obj
的值为undefined
,在赋值操做时,javascript
引擎会在内存里建立一个下面的对象,而后将这个对象的内存地址赋值给 obj
这个变量。
{
name: 'a',
say: function(){
console.log(this.name)
}
}
复制代码
若要读取 obj.name
,引擎会先拿到obj
的内存地址,而后到这个内存地址中找到name
这个属性的value
属性。
当给对象赋值的是一个函数时,如上面的obj.say
的地址,引擎会把函数单独保存在内存中,而后再将函数的地址赋值给say
属性的value
属性。
{
name: {
[[value]]: 'a'
[[writable]]: true
[[enumerable]]: true
[[configurable]]: true
},
say: {
[[value]]: 函数的地址
[[writable]]: true
[[enumerable]]: true
[[configurable]]: true
}
}
复制代码
大概的内存结构是这样的
这张图就能够很清晰的看出来了,由于函数是一个独立的地址,那么执行var say = obj.say;
时,实际上是把函数的地址赋值给了say
,那么say
就能够独立运行了,当执行say
时,引擎能够直接拿到函数的地址而再也不经过obj
的内存了。
大概的结构是这样的
js容许在函数体内部,引用当前环境的其余变量。
好比下面的代码,x表明着当前环境的变量。
var f = function(){
console.log(x);
}
复制代码
那么问题来了,函数能够在不一样的环境里运行,那么怎么获取到它的当前运行环境呢?因此,this就出现了,它的设计目的就是在函数体内部,指代函数当前的运行环境。
var x = 'a';
var f = function(){
console.log(this.x);
}
var obj = {
f:f,
x: 'b'
}
f(); //a 在全局环境下单独运行,this指代全局环境,全局的x为a
obj.f(); //b 在obj中运行,this指代obj环境,obj的x 为b
复制代码
常见的有两种,一种是直接在全局运行,函数内部的this直接绑定到全局,一个是经过对象调用,函数内部的this绑定到调用他的对象上。
除了常见的两种,还有两种:
1.是从一个环境传到另外一个环境中,须要使用
call
和apply
,俗称改变this的指向。
2.不绑定。箭头函数独有,此时的this继承于做用域链的上一层,这里难免要聊一下做用域链上的this了。
仍是用上面的粒子
var x = 'a';
var f = function(){
console.log(this.x);
}
var obj = {
f:f,
x: 'b'
}
f(); //a 在全局环境下单独运行,this指代全局环境,全局的x为a
f.call(obj, x); //b
f.apply(obj, [x]); //b
复制代码
call
和apply
都是将this绑定到它第一个参数上。若其第一个参数不是对象,则会使用js的类型强制将第一个参数转为对象。
bind
和call
、apply
不同的是,它会生成一个新的函数,且这个新生成的函数的this
将永久地被绑定到了bind
的第一个参数。具体缘由还不清楚,如有知悉的望告知。
继续上面的那个粒子
var g = f.bind(obj);
var obj2 = {
x: 'c'
}
var h = g.bind(obj2);
g(); //b
h(); //b g的this指向不会被bind改变
复制代码
箭头函数不绑定this,此时的this继承于做用域链的上一层
var x = 1;
function a(){ //函数做用域上没有x,向上找,最后找到window
setTimeout(() => {
console.log(this.x);
})
}
function c(){ //函数做用域上没有x,向上找,最后找到window
setTimeout(() => {
console.log(this.x);
})
}
var obj = {
x: 2, //对象的原型链上有x
c: c
}
var obj1 = {//对象的原型链上没有x
c: c
}
a(); //1 执行环境是全局
obj.c(); //2 执行环境是对象
obj1.c(); //undefined 执行环境是对象
复制代码
因为箭头函数没有本身的this指针,经过 call() 或 apply(),bind()方法调用一个函数时,只能传递参数,不能绑定this,他们的第一个参数会被忽略。
var x = 1;
var a = (b) =>{ //函数做用域上没有x,向上找,最后找到window
console.log(this.x + b);
}
var obj = {
b: 2,
x: 3
}
a.call(obj, obj.b); //3
复制代码
由于写到这道题,发现本身并不清楚为何结果是window,而后翻了下this的原理,算是作一份总结吧。 回顾一下上面的内容,由于js引擎在生成对象的内存时,obj这个变量还未被赋值,因此它的值为undefined,在es5中以明确,当bind第一个参数为undefined时,在浏览器上将执行window。
var obj = {
say: function () {
function _say() {
console.log(this)
}
return _say.bind(obj)
}()
}
obj.say() //window
复制代码
如有理解不合理的地方,还望指正。