前言:
如果你有其他语言的开发经验并未对 JavaScript 进行深入了解,也许会认为两者中的 this 相差无几……
好吧!不管你是不是,文章已写,求你看还不行 ??
令人困惑的 this
思考下列代码:
function say(){
console.log("Hello,world!");
this.count++;
}
say.count = 0;
var count = 0;
for(var i = 0;i < 10;i++)
say();
console.log(say.count);
console.log(count);
复制代码
通常而言,我们会认为 this
应该指向包含他的函数对象。所以,代码中的 this.count
应该是 say.count
,而非 var count
,所以最终输出为 10 ,0;
然而呢?
say.count
居然从未增加!可见,this
并非总是指向函数自身。这其中必有什么“内幕”,等待我们去揭开!
细心的你大概发现了,this.count
指向了全局变量 count
,说明,this
并没错,错的是我们对它的理解。
this 从何而来?
事实上,this
实在运行时被确定的:
当一个函数被调用时,函数的执行上下文将创建,其中包含了 this
。(执行上下文)。而执行上下文中的 this
绑定取决于函数的调用方式,如果被一个对象调用,那么 this
指向这个对象。
简明地说,函数被 谁 调用,那么 谁 就是 this
(函数中的)。
调用的位置到底是哪
你是否已经“夜郎自大”了?请接受好 JavaScript 的毒打!
function hello(){
console.log("Hello,world!");
this.count++;
}
function say(){
hello();
}
say.count = 0;
var count = 0;
for(var i = 0;i < 10;i++)
say();
console.log(say.count);
console.log(count);
复制代码
“啊,就这???”—— hello
调用在 say
里面,所以 this.count++
是 say.count++
而不是 count++
。
嗯嗯,很不错……你猜对个毛!
“你才傻了吧,教我错的!你自己说的 this 取决于调用位置。”
别急,调用位置虽然确实是函数的调用位置。但这个调用位置并非仅仅包含调用函数就是其调用位置了。这得解释调用栈:
调用栈是解释器(比如浏览器中的 JavaScript 解释器)追踪函数执行流的一种机制。当执行环境中调用了多个函数时,通过这种机制,我们能够追踪到哪个函数正在执行,执行的函数体中又调用了哪个函数。
看调用 hello
的地方:hello
是直接被访问到的(中间没有经过任何函数对象)。所以,调用它的还是全局对象,它的执行上下文还是全局执行上下文。
也就是说,如果我们要让 this
指向 say
,应该通过 say
去访问 hello
?:
function hello(){
console.log("Hello,world!");
this.count++;
}
function say(){
say.helloFun();//此时是 say-> helloFun
}
say.helloFun = hello;//指向 hello
say.count = 0;
var count = 0;
for(var i = 0;i < 10;i++)
say();
console.log(say.count);
console.log(count);
复制代码
果然,this
终于指向 say
了:
在进行更深入的了解之前,是时候接受终极考验了;
function hello(){
console.log("Hello,world!");
this.count++;
}
function say(){
say.helloFun();
}
say.count = 0;
var count = 0;
for(var i = 0;i < 10;i++)
(say.helloFun = hello)();
console.log(say.count);
console.log(count);
复制代码
say.count
是 10,还是 0 ?答案在最后。
this 的绑定规则
默认绑定
在任何函数体外,this
指向全局对象(如浏览器是 window
)。
在函数内部,this
指向函数被调用的位置。但值得注意的是,严格模式下,如果函数不被任何函数调用,this
将保持为 undefined
(不默认指向 window
)。
隐式绑定
也就是前面所说: this
取决于调用的位置。
但有些情况会“解除”隐式绑定——我们经常会希望把一个函数对象传给另一个函数去执行:
然而并没得到预期的结果。细细分析其实很好理解:fun
方法接收到的参数实际上还是指向 say
函数,其中并未经过 obj
对象。
显示绑定
我们会希望,obj
里的 say
函数只得绑定 obj
,JavaScript 中的 call
方法可以满足我们的需求:
var obj = {
count :0,
say:function(){
console.log("Hello,world!")
this.count++;
}
}
function fun(say){
say.call(obj);
}
var count = 0;
for(var i = 0;i < 10;i++)
fun(obj.say);
console.log(obj.count);
console.log(count);
复制代码
call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。
fun
函数中,通过 say.call(obj);
强制把 say
的 this
绑定到了 obj
上。
我们可以通过 bind
函数快速做到绑定:
bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。
new 绑定
当我们使用 new
来调用函数时,会发生以下操作:
- 创建一个空的 JavaScript 对象(即{});
- 链接该对象(设置该对象的 constructor )到另一个对象 ;
- 将 步骤1 新创建的对象作为 this 的上下文 ;
- 如果该函数没有返回对象,则返回 this 。
所以以下代码就不足为奇了:
绑定顺序
有时多种的绑定规则会运用在同一个对象上,这时的 this 绑定遵循以下规则:
- 由 new 调用则绑定到新创建的对象。( new 绑定)
- 由 call 、apply 或 bind 调用则绑定到指定的对象。(显示绑定)
- 由上下文对象调用则绑定到该上下文对象。(隐式绑定)
4、无法套用任何规则:绑定到全局对象,严格模式下为 undefined(默认绑定)。
你也许会发现:隐式绑定和默认绑定在非严格模式下结果是一样的。
总结
我写的太好了,还用总结???
不吹了,点个赞bie~?!