Begin
这是一篇老掉牙的写javascript中this指向的文章。暴露是小白了...
其实挺简单的,找一下调用点在哪里,看看适用la个规则,看看是不是特例,就知道了。嗯...
让我们开始吧!
找调用点
this
指向哪个对象,不取决函数如何被声明,取决于函数如何被调用。这被称为调用点。借助chrome的开发者工具,找到调用栈,从上往下的第二个记录就是真正的调用点。(如果不能用辅助工具就自己找了~)
看一些例子:
切换到Sources,在foo
函数内打一个断点,刷新页面重新执行,右侧栏Call Stack第一个记录foo说明foo
函数在执行,第二个记录是真正的调用点,双击它!
可以看见跳转到foo();
这条语句,函数的调用点,在右侧栏可以看到具体在index.js文件的第7行。
上面例子得到的函数调用点是一目了然的,也有情况得到的结果是间接的:
这里foo
函数的调用点却是bar()
,需要找一下bar
是什么,var bar = obj.foo
同样的找一下var bar = foo.bind(obj)
总结来说,使用chrome开发者工具call stack找调用点的办法,大多数情况得到的结果是直接的,少数情况得到的结果是间接的,需要再找一下,比如上面两个例子中的bar
。
规则+特例
找到调用点后,注意力转移到调用点如何确定this
指向。上高中那会儿,化学老师对某一类型的题目老是强调:记住大多数的规律,加上少数的特例,你就记全了。
有四个规则
默认绑定规则:独立函数调用(我称之为函数裸奔调用),函数内容在strict mode
下?this=undefined
:this=window
。看些例子:
function foo() {
console.log( this.a );
}
var a = 2;
foo(); // 2 裸奔调用~
复制代码
//demo1
function Bar() {
"use strict";
console.log( this.a );
}
var a = 2;
Bar(); // TypeError: `this` is `undefined` 裸奔调用~
//demo2
'use strict';
function test() {
console.log(this);
};
test();// undefined 裸奔调用~
//demo3
function foo() {
console.log( this.a );
}
var a = 2;
(function(){
"use strict"; //调用点在严格模式下而不是函数内容在严格模式下,this指向window
foo(); // 2
})();
复制代码
隐含绑定规则:形如obj.fn
,调用点用obj
对象来引用函数,this
指向obj
。看些例子:
function foo() {
console.log( this.a );
}
var obj = {
a: 2,
foo: foo
};
obj.foo(); // 2
复制代码
//只有对象属性引用链最后一层影响调用点
//结果是42而不是2
function foo() {
console.log( this.a );
}
var obj2 = {
a: 42,
foo: foo
};
var obj1 = {
a: 2,
obj2: obj2
};
obj1.obj2.foo(); // 42
复制代码
明确绑定规则:在调用点强行指定this
指向。比如使用call()
、apply()
、bind()
和某些API提供一个可选参数指定环境。看些例子:
//使用call
function foo() {
console.log( this.a );
}
var obj = {
a: 2
};
foo.call( obj ); // 2
复制代码
//forEach参数指定
function foo(el) {
console.log( el, this.id );
}
var obj = {
id: "awesome"
};
// 使用 `obj` 作为 `this` 来调用 `foo(..)`
[1, 2, 3].forEach( foo, obj ); // 1 awesome 2 awesome 3 awesome
复制代码
new
绑定规则:使用new
的函数的构造器调用。看个例子,以这个例子解释一下new
调用的四个步骤:
- 一个全新的对象会凭空创建(就是被构建):
obj
- 这个新构建的对象会被接入原形链 :
obj.__proto__=foo.prototype
- 这个新构建的对象被设置为函数调用的
this
绑定 :foo函数内this
指向obj
- 除非函数返回一个它自己的其他 对象,否则这个被
new
调用的函数将 自动 返回这个新构建的对象 :foo函数没有返回新对象,bar = obj
function foo(a) {
this.a = a;
}
var bar = new foo( 2 );
console.log( bar.a ); // 2
复制代码
调用点适用多种规则,规则有优先顺序:
-
new绑定、明确绑定>隐含绑定>默认绑定
比如
new obj.foo()
和obj.foo(obj1)
是按照new绑定和明确绑定节奏走的 -
new绑定>硬绑定(new绑定无法和call、apply一起使用)
应用是一种“柯里化”,预先设置函数的部分或所有参数
function foo(p1,p2) { this.val = p1 + p2; } // 在这里使用 `null` 是因为在这种场景下我们不关心 `this` 的硬绑定 // 而且反正它将会被 `new` 调用覆盖掉! var bar = foo.bind( null, "p1" ); var baz = new bar( "p2" ); baz.val; // p1p2 复制代码
特例
-
隐含丢失(函数赋值、函数作为参数传递,创建函数的间接引用):隐含绑定规则无效,回到默认绑定的规则。看些例子:
function foo() { console.log( this.a ); } var obj = { a: 2, foo: foo }; var bar = obj.foo; // 函数间接引用! var a = "oops, global"; // `a` 也是一个全局对象的属性 bar(); // "oops, global" 复制代码
function foo() { console.log( this.a ); } var obj = { a: 2, foo: foo }; var a = "oops, global"; // `a` 也是一个全局对象的属性 setTimeout( obj.foo, 100 ); // "oops, global" 复制代码
- 函数作为参数传递,如果想函数执行时保留
this
指向,使用硬绑定。在上例中,foo.bind(obj)
代替obj.foo
。
- 函数作为参数传递,如果想函数执行时保留
-
将
null
或者undefined
作为apply
、call
或者bind
的this
指定值,回到默认绑定的规则。前面提到的柯里化是这一条特例的应用,可惜被new调用覆盖了。再看一个old way参数展开的例子:function foo(a,b){ console.log(a+b); } //参数展开 foo.apply(null,[1,2]); //等价于es6的 foo(...[1,2]) 复制代码
-
如果函数在严格模式下,指定
null
或者undefined
就是null
或者undefined
var a = 2; function foo(){ "use strict"; console.log(this.a); } foo.apply(null); //Uncaught TypeError: Cannot read property 'a' of null 复制代码
-
回到默认绑定规则考虑到污染全局对象,传一个空对象替换
null
或undefined
function foo(a,b){...} //空对象,也被称为DMZ空对象 var ø = Object.create( null ); foo.apply(ø,[2,3]) 复制代码
-
-
!!!箭头函数:封闭它的(函数或全局)作用域
this
是什么,箭头函数this
就指向什么。看些例子:function foo() { // 返回一个箭头函数 return (a) => { // 这里的 `this` 是词法上从 `foo()` 采用的 console.log( this.a ); }; } var obj1 = { a: 2 }; var obj2 = { a: 3 }; var bar = foo.call( obj1 ); bar.call( obj2 ); // 2, 不是3! 复制代码
function foo() { setTimeout(() => { // 这里的 `this` 是词法上从 `foo()` 采用 console.log( this.a ); },100); } var obj = { a: 2 }; foo.call( obj ); // 2 复制代码
箭头函数的本质是词法作用域(和调用点决定的机制不一样)。上面例子在ES6之前,常这样写:
function foo() { var self = this; // 词法上捕获 `this` setTimeout( function(){ console.log( self.a ); }, 100 ); } var obj = { a: 2 }; foo.call( obj ); // 2 复制代码
其他
最后记录一个有意思的demo和一点想法。
有意思的demo能做的是:默认绑定规则适用时,this
指向window
,现在想自己指定默认值,称为软绑定。看代码:
//softBind方法
if (!Function.prototype.softBind) {
Function.prototype.softBind = function(obj) {
var fn = this,
curried = [].slice.call( arguments, 1 ),
bound = function bound() {
return fn.apply(
(!this ||
(typeof window !== "undefined" &&
this === window) ||
(typeof global !== "undefined" &&
this === global)
) ? obj : this,
curried.concat.apply( curried, arguments )
);
};
bound.prototype = Object.create( fn.prototype );
return bound;
};
}
//使用
function foo() {
console.log("name: " + this.name);
}
var obj = { name: "obj" },
obj2 = { name: "obj2" },
obj3 = { name: "obj3" };
var fooOBJ = foo.softBind( obj );
fooOBJ(); // name: obj
obj2.foo = foo.softBind(obj);
obj2.foo(); // name: obj2 <---- 看!!!
fooOBJ.call( obj3 ); // name: obj3 <---- 看!
setTimeout( obj2.foo, 10 ); // name: obj <---- 退回到软绑定
复制代码
其中curried = [].slice.call( arguments, 1 )
和curried.concat.apply( curried, arguments )
两行代码实现了柯里化(bind
函数也有),还用到了闭包。
一点想法是:既然this
是在函数调用的时候被确定,那能不能假想函数如何被调用,来写函数?
补充:手动实现apply
、call
和bind
:
- 使用隐含绑定改变
this
指向 - 使用ES6扩展符解决参数传递问题
- 细节处理:检测传入对象是否是特殊的
null
、undefined
、string
、number
或者boolean
;删除隐含绑定添加的属性
Function.prototype.calldiy = function(target, ...args) {
//检测target不同情况
if(target === null || target === undefined){
target = window;
}else{
target = Object(target);
}
target.foo = this;
let temp = target.foo(...args);
delete target.foo; //删除添加的foo属性
return temp;
}
Function.prototype.applydiy = function(target, args) {
//检测target不同情况
if(target === null || target === undefined){
target = window;
}else{
target = Object(target);
}
target.foo = this;
let temp = target.foo(...args);
delete target.foo; //删除添加的foo属性
return temp;
}
Function.prototype.binddiy = function(target, ...args1) {
// let fn = this;
// return function(...args2) {
// fn.applydiy(target, args1.concat(args2));
// }
return (...args2) => this.applydiy(target, args1.concat(args2));
}
复制代码
End
文章为个人总结,不妥之处还请雅正。
转载请注明出处。
参考文献: