摘要:在中其实是一颗语法糖,但是这糖有毒。致命的地方在于它的指向往往不能直观确定。希望下面可以一步步去掉有毒的糖衣。这样理解可能有些极端,但是它可能有助于避免一些常见的错误。第三步一个传递参数更好的办法仍存在两个安全隐患。
在 JavaScript 中 this 其实是一颗语法糖,但是这糖有毒。this 致命的地方在于它的指向往往不能直观确定。希望下面可以一步步去掉有毒的糖衣。
1 用 f.call(thisVal, ...args) 指定 this调用函数的方式有三种,用 Function.prototype.call 调用可以指定 this:
定义 function f(...args){/*...*/}
调用 f.call(thisVal, ...args);
例一
function greet(){ console.log("Hello, " + this); } // 手动指定 `greet` 中的 `this`: greet.call("ngolin"); // Hello, ngolin
例二
function whoAreYou(){ console.log("I"m " + this.name); } whoAreYou.call({name: "Jane"}); // I"m Jane2 使用语法糖,this 自动指定
先接受函数 f 的正确调用方式是 f.call(thisVal, ...args);, 然后就可以把 f(...args); 理解成语法糖。
但是不用 f.call(thisVal, ...args), this 怎样动态指定?
一、函数(function)
// 1. 在非严格模式下:window f(); // 解糖为 f.call(window); // 2. 但在严格模式下:undefined f(1, 2, 3); // 解糖为 f.call(undefined, 1, 2, 3);
一、方法(method)
// 无论是在严格还是非严格模式: 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 的行为(当然这种变化在调用时才会体现)。下面先看函数转方法,再看方法转函数。
3 函数转方法函数声明(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()... 的方式),而不是在哪里定义。
但是,为了表达的方便,这里仍然会使用定义函数和定义方法这两种说法。
4 方法转函数将方法转成函数比较容易出错,比如:
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)在一个对象上,从而无论怎样调用,它都能够正常执行。
5 把方法绑定(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
存在两个安全隐患:
1 后续改变 obj.method,比如 obj.method = null;
2 后续改变 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"6 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));7 常见问题及容易出错的地方
一 在定义对象时有没有 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(); } }
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/89453.html
摘要:输出的作用与和一样,都是可以改变函数运行时上下文,区别是和在调用函数之后会立即执行,而方法调用并改变函数运行时上下文后,返回一个新的函数,供我们需要时再调用。 前言 js中的call(), apply()和bind()是Function.prototype下的方法,都是用于改变函数运行时上下文,最终的返回值是你调用的方法的返回值,若该方法没有返回值,则返回undefined。这几个方法...
摘要:在和中都保留了数组的强引用,所以在中简单的清除变量内存并没有得到释放,因为还存在引用计数。而在中,它的键是弱引用,不计入引用计数中,所以当被清除之后,数组会因为引用计数为而被回收掉。其实我们主要注意的引用是不计引用计数的,就好理解了。 showImg(https://segmentfault.com/img/remote/1460000019147368?w=900&h=383); 前...
摘要:中基本都使用来开发,但其实是的一种语法糖。但是我们必须知道,本质上就是在编译的时候,会由将转化为。比如生成了比如生成了解的本质,只需要记住本质就是附录提供的一个在线转换为的地址 react中基本都使用JSX来开发,但JSX其实是javascript的一种语法糖。 什么是语法糖? 语法糖就是提供了一种全新的方式书写代码,但是其实现原理与之前的写法相同。语法糖可以说是广泛存在于各种计算机...
摘要:题目要求假设有个孩子站成一排,每个孩子拥有一个评估值。我们可以观察到,每次最远只需要额外分发到距离当前最近的评分最高的那个孩子。因为他的糖果数量的增加并不会影响到之前孩子。当有多个最近评分最高的孩子时,则选择最后一个。 题目要求 There are N children standing in a line. Each child is assigned a rating value....
摘要:贪心法复杂度时间空间思路典型的贪心法,如果一个孩子比另一个孩子的分高,我们只多给块糖。我们可以先从左往右遍历,确保每个孩子根他左边的孩子相比,如果分高,则糖要多个,如果分比左边低,就只给一颗。 Candy There are N children standing in a line. Each child is assigned a rating value. You are gi...
阅读 2934·2021-09-22 15:52
阅读 2885·2019-08-30 15:55
阅读 2685·2019-08-30 15:53
阅读 2437·2019-08-30 13:21
阅读 1581·2019-08-30 13:10
阅读 2432·2019-08-26 12:09
阅读 2539·2019-08-26 10:33
阅读 1787·2019-08-23 18:06