-
Notifications
You must be signed in to change notification settings - Fork 4.7k
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
JavaScript深入之bind的模拟实现 #12
Comments
先来个沙发,等会有时间看 |
哈哈,欢迎光临。@jawil |
前辈,好像有一个typo。 |
哈哈,确实是写错了,本来是想写habit,没有想到hobbit写的太顺手,我竟然没有任何违和的感觉……感谢指出哈~ |
我把最后的实现代码跑了一下构造函数的例子 发现this依然失效了啊 是什么问题呢 |
@enjkvbej 作为构造函数时,this 就是会失效呐 |
😄 |
@jawil 说起来,博主的 V8 源码系列写得怎么样了?很好奇第一篇会讲什么? |
V8 源码系列从入门到放弃,卒 |
fNOP.prototype = this.prototype; 是不是就等于fbound.prototype = Object.create(this.prototype); |
@fbsstar 是的,Object.create 的模拟实现就是: Object.create = function( o ) {
function f(){}
f.prototype = o;
return new f;
}; |
对第三版模拟实现代码进行了优化。以前是 this instanceof self ? this : context 现在改成了 this instanceof fBound ? this : context 因为 |
为什么要设置 fBound.prototype = this.prototype,只是为了继承一下绑定函数的原型对象中的属性吗? |
@caiyongmin 为了让 fBound 构造的实例能够继承绑定函数的原型中的值 |
我的意思是,为什么要继承? |
@caiyongmin 因为原生的 bind 的效果就是这样呐 |
您好,我们直接将 fBound.prototype = this.prototype,我们直接修改 fBound.prototype 的时候,也会直接修改绑定函数的 prototype。这里有点不太懂诶,能多讲解一下吗,谢谢 |
@liuxinqiong 我们来写个 demo 哈: Function.prototype.bind2 = function (context) {
var self = this;
var args = Array.prototype.slice.call(arguments, 1);
var fBound = function () {
var bindArgs = Array.prototype.slice.call(arguments);
self.apply(this instanceof fBound ? this : context, args.concat(bindArgs));
}
fBound.prototype = this.prototype;
return fBound;
}
function bar() {}
var bindFoo = bar.bind2(null);
bindFoo.prototype.value = 1;
console.log(bar.prototype.value) // 1 你会发现我们明明修改的是 bindFoo.prototype ,但是 bar.prototype 的值也被修改了,这就是因为 |
@mqyqingfeng 万分感谢,点破之后,对之前的知识都有了新的认识!已经第二次看了,每次都有收获! |
@liuxinqiong 哈哈,感谢肯定~ 加油哈~ |
建议第三版中 |
@youzaiyouzai666 感谢指出,改成 self 能避免理解混乱,确实更好一些~ 关于 es6 的写法,我给自己的要求是在没写 ES6 系列之前,尽量保持 ES5 的写法,这是希望看这个系列的初学者们不要有额外的学习成本 |
看到写
分享一下 |
@baixiaoji 感谢分享哈~ 不过这段代码并没有完整的实现 bind 的特性,比如 "当 bind 返回的函数作为构造函数的时候,bind 时指定的 this 值会失效" var value = 2;
var foo = {
value: 1
};
function bar(name, age) {
console.log(this.value);
}
var bindFoo = bar.bind(foo);
var obj = new bindFoo('18'); 使用原生的 bind 就会返回 undefined,使用这段代码的话,就会返回 1 |
最终版代码应该是 this instanceof fBound 而不是 this instanceof fNOP 吧 |
Array.prototype.slice.call(arguments) ,这里调用 |
@1sm23 |
贴下我的写法: Function.prototype.customBind = function (thisArg, ...bindArgs) {
const self = this
function tempFunc() {}
tempFunc.prototype = self.prototype
bindFunc.prototype = new tempFunc()
function bindFunc(...argsArray) {
return self.apply(new.target ? this : thisArg, [...bindArgs, ...argsArray])
}
return bindFunc
} 可以使用 |
可以的,个人认为 为了避免寄生继承那部分代码出错,还是用原型链来判断比较好,new调用的时可以是一个构造函数,但是constructor的指向可能出错
lliiooiill ***@***.***> 于2021年12月5日周日 10:45写道:
… 贴下我的写法:
Function.prototype.customBind = function (thisArg, ...bindArgs) {
const self = this
function tempFunc() {}
tempFunc.prototype = self.prototype
bindFunc.prototype = new tempFunc()
function bindFunc(...argsArray) {
return self.apply(new.target ? this : thisArg, [...bindArgs, ...argsArray])
}
return bindFunc
}
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
<#12 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AGJM2EMUIME23JW6DEBHXN3UPLG4ZANCNFSM4DJ3R5UQ>
.
Triage notifications on the go with GitHub Mobile for iOS
<https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675>
or Android
<https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub>.
|
|
这是来自QQ邮箱的假期自动回复邮件。你好,我最近正在休假中,无法亲自回复你的邮件。我将在假期结束后,尽快给你回复。
|
有一个小小的问题,原生的 bind 方法 生成的 boundFn,用 new 调用得到的实例并不会继承 boundFn function foo(name, age) {
console.log(this.a)
this.name = name
this.age = age
}
let boundFoo = foo.bind({a: 1}, 'Bob')
boundFoo.prototype // undefined
boundFoo.prototype = Object.create(null)
boundFoo.prototype.say = function() {
console.log(this.name)
}
let ins = new boundFoo(24)
ins.say // undefined 看了下 code-js 的实现,它是直接返回 var construct = function (C, argsLength, args) {
if (!(argsLength in factories)) {
for (var list = [], i = 0; i < argsLength; i++) list[i] = 'a[' + i + ']';
factories[argsLength] = Function('C,a', 'return new C(' + list.join(',') + ')');
}
return factories[argsLength](C, args);
};
module.exports = function bind(that /* , ...args */) {
var fn = aFunction(this);
var partArgs = slice.call(arguments, 1);
var boundFunction = function bound(/* args... */) {
var args = partArgs.concat(slice.call(arguments));
return this instanceof boundFunction ? construct(fn, args.length, args) : fn.apply(that, args);
};
// to make instanceof to work for boundFunction
if (isObject(fn.prototype)) boundFunction.prototype = fn.prototype;
return boundFunction;
}; |
对于这个demo我有疑问: const module = {
x: 42,
getX: function() {
return this.x
}
};
const unboundGetX = module.getX;
const boundGetX = unboundGetX.bind(module);
console.log(boundGetX.prototype); // undefined
console.log(Function.prototype.bind.prototype) // undefined 实际上无论是 |
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;
} |
受益匪浅,谢谢羽哥 |
bind,apply,call的区别和实现 bind,apply,call都是用来改变this指向,而不同点如下:
下面是bind,apply,call的实现: |
现在最新的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邮箱的假期自动回复邮件。你好,我最近正在休假中,无法亲自回复你的邮件。我将在假期结束后,尽快给你回复。
|
我觉得你这样实现没问题 |
这是来自QQ邮箱的假期自动回复邮件。你好,我最近正在休假中,无法亲自回复你的邮件。我将在假期结束后,尽快给你回复。
|
这是来自QQ邮箱的假期自动回复邮件。
您好,我最近正在休假中,无法亲自回复您的邮件。我将在假期结束后,尽快给您回复。
|
|
这是来自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邮箱的假期自动回复邮件。
您好,我最近正在休假中,无法亲自回复您的邮件。我将在假期结束后,尽快给您回复。
|
|
最终代码 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
}
这里的
要么 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 这时候 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() 输出报错:
这时候 console.log('obj2GetName :>> ', obj2GetName.prototype)
// obj2GetName :>> undefined 也就是: fNOP.prototype = this.prototype = undefined 如果把上面提到的第 1 处可以修改的 fBound.prototype = new fNOP() = {}
或者第 2 处 if (this.prototype) {
// Function.prototype doesn't have a prototype property
fNOP.prototype = this.prototype
} 那么 var fNOP = function () {}
fNOP.prototype = {}
最后说一下在输出报错那里,为什么会有 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,对作者也是一种鼓励。
The text was updated successfully, but these errors were encountered: