-
Notifications
You must be signed in to change notification settings - Fork 4.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in 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邮箱的假期自动回复邮件。
您好,我最近正在休假中,无法亲自回复您的邮件。我将在假期结束后,尽快给您回复。
|
https://github.com/zloirock/core-js/blob/master/packages/core-js/internals/function-bind.js#L3
|
最终代码 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: