在 JavaScript 中 this
实际上是一颗语法糖,可是这糖有毒。this
致命的地方在于它的指向每每不能直观肯定。但愿下面能够一步步去掉有毒的糖衣。安全
f.call(thisVal, ...args)
指定 this
调用函数的方式有三种,用 Function.prototype.call
调用能够指定 this
:闭包
定义 function f(...args){/*...*/}
app
调用 f.call(thisVal, ...args);
框架
例一函数
function greet(){ console.log('Hello, ' + this); } // 手动指定 `greet` 中的 `this`: greet.call('ngolin'); // Hello, ngolin
例二this
function whoAreYou(){ console.log("I'm " + this.name); } whoAreYou.call({name: 'Jane'}); // I'm Jane
this
自动指定先接受函数 f
的正确调用方式是 f.call(thisVal, ...args);
, 而后就能够把 f(...args);
理解成语法糖。prototype
可是不用 f.call(thisVal, ...args)
, this
怎样动态指定?code
1、函数(function)对象
// 1. 在非严格模式下:window f(); // 解糖为 f.call(window); // 2. 但在严格模式下:undefined f(1, 2, 3); // 解糖为 f.call(undefined, 1, 2, 3);
1、方法(method)ip
// 不管是在严格仍是非严格模式: obj.m(1, 2, 3); // 解糖为 obj.m.call(obj, 1, 2, 3); obj1.obj2.m(...args); // obj1.obj2.m.call(obj1.obj2, ...args); obj1.obj2....objn.m(); // obj1.obj2....objn.m.call(obj1.obj2....objn);
经过上面的例子,分别演示了函数 f(..args)
和方法 obj1.obj2....objn.m(..args)
怎样自动指定 this
.
严格区分函数(function)和方法(method)这两个概念有利于清晰思考,由于它们在绑定 this
时发生的行为彻底不同。同时函数和方法能够相互赋值(转换),在赋值先后,惟一发生变化的是绑定 this
的行为(固然这种变化在调用时才会体现)。下面先看函数转方法,再看方法转函数。
函数声明(function f(){}
)和函数表达式(var f = function(){};
)有一些微妙的区别,可是两种方式在调用时绑定this
行为彻底同样,下面在严格模式下以函数表达式为例:
var f = function(){ console.log(this.name); }; var obj1 = { name: 'obj 1', getName: f; }; var obj2 = { name: 'obj 2', getName: f; }; // 函数 `f` 转方法 `obj1.getName` obj1.getName();// 'obj 1' => obj1.getName.call(obj1) // 不认为函数转方法 obj2.getName.call(obj1);// 'obj 1'(不是 'obj 2')
将函数转成方法一般不太容易出错,由于起码在方法中 this
可以有效地指向一个对象。函数转成方法是一个模糊的说法,实际上能够这样理解:
JavaScript 不能定义一个函数,也不能定义一个方法,是函数仍是方法,要等到它执行才能肯定;当把它当成函数执行,它就是函数,当把它当成方法执行,它就是方法。因此只能说执行一个函数和执行一个方法。\
\
这样理解可能有些极端,可是它可能有助于避免一些常见的错误。由于关系到this
怎样绑定,重要的是在哪里调用(好比在obj1
,obj2
... 上调用)以及怎样调用(好比以f()
,f.call()
... 的方式),而不是在哪里定义。
可是,为了表达的方便,这里仍然会使用定义函数和定义方法这两种说法。
将方法转成函数比较容易出错,好比:
var obj = { name: 'obj', show: function(){ console.log(this.name); } }; var _show = obj.show; _show(); // error!! => _show.call(undefined) button.onClick = obj.show; button.onClick(); // error!! => button.onClick.call(button) (function(cb){ cb(); // error!! =>cb.call(undefined) })(obj.show);
当一个对象的方法使用了 this
时,若是这个方法最后不是由这个对象调用(好比由其余框架调用),这个方法就可能会出错。可是有一种技术能够将一个方法(或函数)绑定(bind)在一个对象上,从而不管怎样调用,它都可以正常执行。
先看这个obj.getName
的例子:
var obj = { getName: function(){ return 'ngolin'; } }; obj.getName(); // 'ngolin' obj.getName.call(undefined); // 'ngolin' obj.getName.call({name: 'ngolin'}); // 'ngolin' var f = obj.getName; f(); // 'ngolin' (function(cb){ cb(); // 'ngolin' })(obj.getName);
上面的例子之因此能够成功是由于 obj.getName
根本没有用到 this
, 因此 this
指向什么对 obj.getName
都没有影响。
这里有一种技术把使用 this
的方法转成不使用 this
的方法,就是建立两个闭包(即函数),第一个闭包将方法(method)和对象(obj)捕获下来并返回第二个闭包,而第二个闭包用于调用并返回 obj.method.call(obj);
. 下面一步步实现这种技术:
第一步 最简单的状况下:
function method(){ obj.method.call(obj); } method(); // correct, :))
存在的缺陷:
obj.method
obj.method
,好比 obj.method = null;
obj
,好比 obj = null
第二步 在方法有参数有返回的状况下:
function method(a, b){ return obj.method.call(obj, a, b); } method(a, b); // correct, :))
存在的缺陷:
obj.method
第三步 一个传递参数更好的办法:
function method(){ return obj.method.apply(obj, arguments); } method(a, b); // correct, :))
仍存在两个安全隐患。
第四步 更加安全的方式:
var method = (function(){ return function(){ return obj.method.apply(obj, arguments); }; })(obj.method, obj); method(a, b); // correct, :))
第五步 抽象出一个函数,用于将方法绑定到对象上:
function bind(method, obj){ return function(){ return method.apply(obj, arguments); }; } var obj = { name: 'ngolin', getName: function(){ return this.name; } }; var method = bind(obj.getName, obj); method(); // 'ngolin'
Function.prototype.bind
这种方法很常见,后来 ECMAScript 5 就增长了 Function.prototype.bind
, 好比:
var binded = function(){ return this.name; }.bind({name: 'ngolin'}); binded(); // 'ngolin'
具体来讲,Function.prototype.bind
这样工做:
var bindedMethod = obj.method.bind(obj); // 至关于: var bindedMethod = (function(){ return function(){ return obj.method.apply(obj, arguments); }; })(obj.method, obj);
更多使用 Function.prototype.bind
的例子:
var f = obj.method.bind(obj); button.onClick = obj.method.bind(obj); document.addEventListener('click', obj.method.bind(obj));
一 在定义对象时有没有 this
?
obj = { firstName: 'First', lastName: 'Last', // `fullName` 能够获得预期结果吗? fullName: this.firstName + this.lastName } // 或者: function makePoint(article){ if(article.length <= 144) return article; return article.substr(0, 141) + '...'; } obj = { fulltext: '...a long article go here...', // `abstract` 呢? abstract: makePoint(this.fulltext) }
二 在方法内的 this
都是同一对象吗?
obj = { count: 3, field: 'field', method: function(){ function repeat(){ if(this.count > 100){ return this.field.repeat(this.count % 100); } this.field.repeat(this.count); }.bind(this); // 这个呢? return repeat(); } }