Repository navigation
JavaScript深入之bind的模拟实现 #12
Description
Activity
先来个沙发,等会有时间看
Reacted by wuhongye0512, zhangyt93, Ruios, YiMeng and Lihuowang-2Reacted by blast_onion哈哈,欢迎光临。@jawil
Reacted by 吴晗君, linnfeng, wuhongye0512, YuHui, Changxin Li and YiMeng前辈,好像有一个typo。
模拟构造函数效果里的代码,有一个hobbit-霍比特人属性,应该是habit-习惯吧?#笑哭哈哈,确实是写错了,本来是想写habit,没有想到hobbit写的太顺手,我竟然没有任何违和的感觉……感谢指出哈~
我把最后的实现代码跑了一下构造函数的例子 发现this依然失效了啊 是什么问题呢
@enjkvbej 作为构造函数时,this 就是会失效呐
😄
@jawil 说起来,博主的 V8 源码系列写得怎么样了?很好奇第一篇会讲什么?
V8 源码系列从入门到放弃,卒
Reacted by 黑眼圈、警告, maizi, tangyun, 董文博, l0ng09, wangxiaoxaio, minl, magicstf, Eugene, 水墨青衫 and 23 moreReacted by mumiao, Rocky Wong, Huang ShuWei, 黑眼圈、警告, win, Yingtao Mo, TothingWay, z-z-w, An Yan, VenenoFSD and 15 moreReacted by 黑眼圈、警告, win, Anoninz, 吉古力, wyj1993 and wuhongye0512Reacted by mumiao, zhangbh and wyj1993Reacted by mumiao and SatoruReacted by 王金平, Satoru, Ygm, banana and AlanGolphifNOP.prototype = this.prototype;
fbound.prototype = new fNOP();是不是就等于fbound.prototype = Object.create(this.prototype);
Reacted by shmily, Shawn Yang, haohong11, lucas, SJK, 赤砚, XiaoJG, Yoki, theWindingPath, smallwebbird and 20 more@fbsstar 是的,Object.create 的模拟实现就是:
Object.create = function( o ) { function f(){} f.prototype = o; return new f; };
Reacted by Skyler, Rocky Wong, JiangZG, Satoru, 10, guochang, 董文博, jq.yao, Zhang, linksee.dev and 55 moreReacted by xipper and 王伟Reacted by 王金平, ericyonghui and moshappleReacted by Kuan Yanbin and Adophlidu对第三版模拟实现代码进行了优化。以前是
this instanceof self ? this : context
现在改成了
this instanceof fBound ? this : context
因为
fNOP.prototype = this.prototype的缘故,两段代码在效果上并没有区别,但是个人觉得改成 fBound 会更好理解, 而且 MDN 也是采用的 fBound 。Reacted by shantyhub为什么要设置 fBound.prototype = this.prototype,只是为了继承一下绑定函数的原型对象中的属性吗?
@caiyongmin 为了让 fBound 构造的实例能够继承绑定函数的原型中的值
171 remaining items
Load more actionsbind,apply,call的区别和实现
bind,apply,call都是用来改变this指向,而不同点如下:
-
bind、apply和call函数的入参第一个都是需要改变this的指向,后面是需要传入的其他参数,但是apply需要传入的其他参数需要放在[数组]里
fn.bind(null, 1,2,3)
fn.call(null, 1,2,3)
fn.apply(null, [1,2,3]) -
apply,call会立即执行,bind不会立即执行,而是返回一个改变了this指向的函数
-
call,apply在第一个参数为undefined或null时,this会指向window
下面是bind,apply,call的实现:
参考
https://blog.csdn.net/qq_30340185/article/details/105677065Reacted by luo and Lean wang-
现在最新的Chrome中,bind执行返回以后的函数上没有原型,修改bind函数的原型,或者给bind返回函数添加原型,都不会相互影响了,所以这是v8源码改了?
- 这是来自QQ邮箱的假期自动回复邮件。你好,我最近正在休假中,无法亲自回复你的邮件。我将在假期结束后,尽快给你回复。
Function.prototype.bind2 = function (context, ...args) { if (typeof this !== 'function') { throw new Error() } context ??= window if (typeof context !== 'object') context = Object(context) // 在原型链上的方法,this 指向的是调用该方法的对象 const self = this return function F() { // 作为构造函数调用 // this 绑定失效 if (this instanceof F) { // 可以使用 new.target 判断是否是 new 调用 // this -> new的新实例 return new self(...args, ...arguments) } // 有返回值 return self.apply(context, [...args, ...arguments]) } }
- 这是来自QQ邮箱的假期自动回复邮件。你好,我最近正在休假中,无法亲自回复你的邮件。我将在假期结束后,尽快给你回复。
var value = 2; var foo = { value: 1 }; function bar(name, age) { this.habit = 'shopping'; console.log(this.value); console.log(name); console.log(age); } bar.prototype.friend = 'kevin'; var bindFoo = bar.bind(foo, 'daisy'); var obj = new bindFoo('18'); // undefined // daisy // 18 console.log(obj.habit); console.log(obj.friend); // shopping // kevin
obj.__proto === bar.prototype,按照文章实现的bind方法 ,两者并不会相等,为什么不用以下的实现办法呢
Function.prototype.bind2 = function (context) { if (typeof this !== "function") { throw new Error("Function.prototype.bind - what is trying to be bound is not callable"); } var self = this; var args = Array.prototype.slice.call(arguments, 1); var fBound = function () { var bindArgs = Array.prototype.slice.call(arguments); if (this instanceof fBound) { return new self(...(args.concat(bindArgs))) } else { return self.apply(context, args.concat(bindArgs)); } } return fBound; }
我觉得你这样实现没问题
- 这是来自QQ邮箱的假期自动回复邮件。你好,我最近正在休假中,无法亲自回复你的邮件。我将在假期结束后,尽快给你回复。
- 这是来自QQ邮箱的假期自动回复邮件。 您好,我最近正在休假中,无法亲自回复您的邮件。我将在假期结束后,尽快给您回复。
Function.prototype.bind = Function.prototype.bind = function(context, ...args) { if (typeof this !== 'function') { throw new TypeError('Type Error'); } const _this = this; function Fn (...args2) { // 当作为构造函数时,this 指向实例,此时结果为 true return _this.call(this instanceof Fn ? this : context, ...args, ...args2); } // 修改返回函数的 prototype 为绑定函数的 prototype,实例就可以继承绑定函数的原型中的值 if (this.prototype) { Fn.prototype = Object.create(this.prototype); } return Fn; }- 这是来自QQ邮箱的假期自动回复邮件。你好,我最近正在休假中,无法亲自回复你的邮件。我将在假期结束后,尽快给你回复。
Function.prototype.bind2 = function (context) { if (typeof this !== "function") { throw new Error("Function.prototype.bind - what is trying to be bound is not callable"); } var self = this; var args = Array.prototype.slice.call(arguments, 1); var fNOP = function () {}; var fBound = function () { var bindArgs = Array.prototype.slice.call(arguments); return self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs)); } fNOP.prototype = this.prototype; fBound.prototype = new fNOP(); return fBound; }
觉得比较难理解的,可以换成下面的来进行思考
Function.prototype.bind2 = function (context, ...args) { if (typeof this !== "function") { throw new Error("Function.prototype.bind - what is trying to be bound is not callable"); } var self = this; var fBound = function (...bindArgs) { return self.apply(this instanceof fBound ? this : context, args.concat(bindArgs)); } fBound.prototype = Object.create(this.prototype) return fBound; }
- 这是来自QQ邮箱的假期自动回复邮件。 您好,我最近正在休假中,无法亲自回复您的邮件。我将在假期结束后,尽快给您回复。
https://github.com/zloirock/core-js/blob/master/packages/core-js/internals/function-bind.js#L3
module.exports = NATIVE_BIND ? $Function.bind : function bind(that /* , ...args */) { var F = aCallable(this); var Prototype = F.prototype; var partArgs = arraySlice(arguments, 1); var boundFunction = function bound(/* args... */) { var args = concat(partArgs, arraySlice(arguments)); return this instanceof boundFunction ? construct(F, args.length, args) : F.apply(that, args); }; if (isObject(Prototype)) boundFunction.prototype = Prototype; // here return boundFunction; };最终代码
Function.prototype.bind2 = function (context) { if (typeof this !== 'function') { throw new Error('Function.prototype.bind - what is trying to be bound is not callable') } var self = this var args = Array.prototype.slice.call(arguments, 1) var fNOP = function () {} var fBound = function () { var bindArgs = Array.prototype.slice.call(arguments) return self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs)) } fNOP.prototype = this.prototype fBound.prototype = new fNOP() return fBound }
- 1
return self.apply(this instanceof fNOP ? this : context, args.concat(bindArgs));这里的
fNOP要么改成fBound- 2
要么
fNOP.prototype = this.prototype;这里改成if (this.prototype) { // Function.prototype doesn't have a prototype property fNOP.prototype = this.prototype }
测试时当直接声明一个函数时:
let obj = { name: 'obj', } function getName() { console.log('getName this.name:>> ', this.name) } let getNameBindObj = getName.bind2(obj) getNameBindObj()
输出:
// getName this.name:>> obj这时候
this instanceof fNOP判断不会出错,因为:console.log('getName :>> ', getName.prototype) // getName :>> {}
也就是:
fNOP.prototype = this.prototype = {}
当函数声明在一个对象里面时:
let obj = { name: 'obj', } let obj2 = { name: 'obj2', getName() { console.log('obj2 getName this.name:>> ', this.name) }, } let obj2GetName = obj2.getName let obj2GetNameBindObj = obj2GetName.bind2(obj) obj2GetNameBindObj()
输出报错:
TypeError: Function has non-object prototype 'undefined' in instanceof check(在 instanceof 校验中 Function 有非对象的 prototype 属性 'undefined')这时候
this instanceof fNOP判断会出错,因为:console.log('obj2GetName :>> ', obj2GetName.prototype) // obj2GetName :>> undefined
也就是:
fNOP.prototype = this.prototype = undefined
如果把上面提到的第 1 处可以修改的
fNOP改成fBound,那么fBound.prototype = new fNOP() = {}
this instanceof fBound判断就不会出错或者第 2 处
fNOP.prototype = this.prototype;这里改成if (this.prototype) { // Function.prototype doesn't have a prototype property fNOP.prototype = this.prototype }
那么
var fNOP = function () {} fNOP.prototype = {}
this instanceof fNOP判断也不会出错
最后说一下在输出报错那里,为什么会有
fNOP.prototype = this.prototype = undefined
不知道在看上面代码的时候有没有注意到 obj2 的 getName 的写法:
let obj2 = { name: 'obj2', getName() { console.log('obj2 getName this.name:>> ', this.name) }, }
这里 getName 用了简写的形式,相当于:
let obj2 = { name: 'obj2', getName: () => { console.log('obj2 getName this.name:>> ', this.name) }, }
原来 getName 是一个箭头函数,箭头函数是没有 prototype 属性的,所以会出现
console.log('obj2GetName :>> ', obj2.getName.prototype) // obj2GetName :>> undefined
如果改成:
let obj2 = { name: 'obj2', getName: function () { console.log('obj2 getName this.name:>> ', this.name) }, }
就没有问题了
console.log('obj2GetName :>> ', obj2.getName.prototype) // obj2GetName :>> {}
所以这里的判断就是为了预防出现箭头函数的形式吧
if (this.prototype) { // Function.prototype doesn't have a prototype property fNOP.prototype = this.prototype }
- 这是来自QQ邮箱的假期自动回复邮件。你好,我最近正在休假中,无法亲自回复你的邮件。我将在假期结束后,尽快给你回复。
bind
一句话介绍 bind:
由此我们可以首先得出 bind 函数的两个特点:
返回函数的模拟实现
从第一个特点开始,我们举个例子:
关于指定 this 的指向,我们可以使用 call 或者 apply 实现,关于 call 和 apply 的模拟实现,可以查看《JavaScript深入之call和apply的模拟实现》。我们来写第一版的代码:
此外,之所以
return self.apply(context),是考虑到绑定函数可能是有返回值的,依然是这个例子:传参的模拟实现
接下来看第二点,可以传入参数。这个就有点让人费解了,我在 bind 的时候,是否可以传参呢?我在执行 bind 返回的函数的时候,可不可以传参呢?让我们看个例子:
函数需要传 name 和 age 两个参数,竟然还可以在 bind 的时候,只传一个 name,在执行返回的函数的时候,再传另一个参数 age!
这可咋办?不急,我们用 arguments 进行处理:
构造函数效果的模拟实现
完成了这两点,最难的部分到啦!因为 bind 还有一个特点,就是
也就是说当 bind 返回的函数作为构造函数的时候,bind 时指定的 this 值会失效,但传入的参数依然生效。举个例子:
注意:尽管在全局和 foo 中都声明了 value 值,最后依然返回了 undefind,说明绑定的 this 失效了,如果大家了解 new 的模拟实现,就会知道这个时候的 this 已经指向了 obj。
(哈哈,我这是为我的下一篇文章《JavaScript深入系列之new的模拟实现》打广告)。
所以我们可以通过修改返回的函数的原型来实现,让我们写一下:
如果对原型链稍有困惑,可以查看《JavaScript深入之从原型到原型链》。
构造函数效果的优化实现
但是在这个写法中,我们直接将 fBound.prototype = this.prototype,我们直接修改 fBound.prototype 的时候,也会直接修改绑定函数的 prototype。这个时候,我们可以通过一个空函数来进行中转:
到此为止,大的问题都已经解决,给自己一个赞!o( ̄▽ ̄)d
三个小问题
接下来处理些小问题:
1.apply 这段代码跟 MDN 上的稍有不同
在 MDN 中文版讲 bind 的模拟实现时,apply 这里的代码是:
多了一个关于 context 是否存在的判断,然而这个是错误的!
举个例子:
以上代码正常情况下会打印 2,如果换成了 context || this,这段代码就会打印 1!
所以这里不应该进行 context 的判断,大家查看 MDN 同样内容的英文版,就不存在这个判断!
(2018年3月27日更新,中文版已经改了😀)
2.调用 bind 的不是函数咋办?
不行,我们要报错!
3.我要在线上用
那别忘了做个兼容:
当然最好是用 es5-shim 啦。
最终代码
所以最最后的代码就是:
下一篇文章
《JavaScript深入系列之new的模拟实现》
相关链接
《JavaScript深入之从原型到原型链》
《JavaScript深入之call和apply的模拟实现》
《JavaScript深入系列之new的模拟实现》
深入系列
JavaScript深入系列目录地址:https://github.com/mqyqingfeng/Blog。
JavaScript深入系列预计写十五篇左右,旨在帮大家捋顺JavaScript底层知识,重点讲解如原型、作用域、执行上下文、变量对象、this、闭包、按值传递、call、apply、bind、new、继承等难点概念。
如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者有所启发,欢迎star,对作者也是一种鼓励。