-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
【进阶3-4期】深度解析bind原理、使用场景及模拟实现 #23
Comments
call2,apply2和bind2的实现,我觉得可以再优化一下。 Function.prototype.call2 = function (context) {
context = context || window;
context.fn = this;
// do else...
} 传入的 function foo() {
console.log(this);
}
foo.call(100); // Number {100} |
谢谢提醒,已更新 |
我用原生的bind试了obj.proto.friend = "Kitty"; // 修改原型 var Foot = bar.bind(foo, "Jack") |
当使用 bind2 返回的函数作为构造函数时,返回的对象类型总是 fBound,而使用原生的 bind 返回的对象类型跟原来类型是一致的。 |
function add(num){
const sum = (arguments[1] || 0) + num;
console.log(sum);
return add.bind(this, sum)
} |
// 第四版,已通过测试用例
} |
function Person(name){
} 这个settimeout是再全局执行得,为什么bind中这个this不是指向window, |
终于看懂bind 模拟实现了 以前看其他的文章 ,总是弄不懂 ,这个this 一会儿判断 是不是function, 一会儿又是instance of 等等 结合作者的例子总算明白了 ,给作者一个大大的赞 |
这里用 hasOwnProperty 来判断 unique_fn 在不在 context 里面会不会有什么问题?万一原型链上有,而执行函数的时候需要用到这个原型链上的函数,但由于 unique_fn 将其覆盖了,导致用到的不是期望的那个原型链上的方法? |
如果传个 if(context === null || context === undefined) {
context = window
} else {
context = Object(context)
} |
我也试了一下,不懂为啥会说是返回错误,原生的bind 也修改了原型,那这里出错的原因,作者也没有解释 |
bind 中的 this 和函数体中所有出现过的 this 都指向调用构造函数后创建的实例,这里就是用 bind 完成一个硬绑定而已 |
这里没有问题。instanceof 中,只要右操作数的原型出现在左操作数的原型链上,就会返回 true。这里的右操作数 fNOP 的原型也就是 |
bind()
语法:
fun.bind(thisArg[, arg1[, arg2[, ...]]])
bind
方法与call / apply
最大的不同就是前者返回一个绑定上下文的函数,而后两者是直接执行了函数。来个例子说明下
通过上述代码可以看出
bind
有如下特性:this
使用场景
1、业务场景
经常有如下的业务场景
这里输出的
nickname
是全局的,并不是我们创建person
时传入的参数,因为setTimeout
在全局环境中执行(不理解的查看【进阶3-1期】),所以this
指向的是window
。这边把
setTimeout
换成异步回调也是一样的,比如接口请求回调。解决方案有下面两种。
解决方案1:缓存
this
值解决方案2:使用
bind
完美!
2、验证是否是数组
【进阶3-3期】介绍了
call
的使用场景,这里重新回顾下。可以通过
toString()
来获取每个对象的类型,但是不同对象的toString()
有不同的实现,所以通过Object.prototype.toString()
来检测,需要以call() / apply()
的形式来调用,传递要检查的对象作为第一个参数。另一个验证是否是数组的方法,这个方案的优点是可以直接使用改造后的
toStr
。上面方法首先使用
Function.prototype.call
函数指定一个this
值,然后.bind
返回一个新的函数,始终将Object.prototype.toString
设置为传入参数。其实等价于Object.prototype.toString.call()
。这里有一个前提是
toString()
方法没有被覆盖3、柯里化(curry)
可以一次性地调用柯里化函数,也可以每次只传一个参数分多次调用。
这里定义了一个
add
函数,它接受一个参数并返回一个新的函数。调用add
之后,返回的函数就通过闭包的方式记住了add
的第一个参数。所以说bind
本身也是闭包的一种使用场景。模拟实现
bind()
函数在 ES5 才被加入,所以并不是所有浏览器都支持,IE8
及以下的版本中不被支持,如果需要兼容可以使用 Polyfill 来实现。首先我们来实现以下四点特性:
this
模拟实现第一步
对于第 1 点,使用
call / apply
指定this
。对于第 2 点,使用
return
返回一个函数。结合前面 2 点,可以写出第一版,代码如下:
测试一下
模拟实现第二步
对于第 3 点,使用
arguments
获取参数数组并作为self.apply()
的第二个参数。对于第 4 点,获取返回函数的参数,然后同第3点的参数合并成一个参数数组,并作为
self.apply()
的第二个参数。测试一下:
模拟实现第三步
到现在已经完成大部分了,但是还有一个难点,
bind
有以下一个特性来个例子说明下:
上面例子中,运行结果
this.value
输出为undefined
,这不是全局value
也不是foo
对象中的value
,这说明bind
的this
对象失效了,new
的实现中生成一个新的对象,这个时候的this
指向的是obj
。(【进阶3-1期】有介绍new的实现原理,下一期也会重点介绍)这里可以通过修改返回函数的原型来实现,代码如下:
this instanceof fBound
结果为true
,可以让实例获得来自绑定函数的值,即上例中实例会具有habit
属性。window
,此时结果为false
,将绑定函数的 this 指向context
prototype
为绑定函数的prototype
,实例就可以继承绑定函数的原型中的值,即上例中obj
可以获取到bar
原型上的friend
。注意:这边涉及到了原型、原型链和继承的知识点,可以看下我之前的文章。
JavaScript常用八种继承方案
模拟实现第四步
上面实现中
fBound.prototype = this.prototype
有一个缺点,直接修改fBound.prototype
的时候,也会直接修改this.prototype
。来个代码测试下:
解决方案是用一个空对象作为中介,把
fBound.prototype
赋值为空对象的实例(原型式继承)。这边可以直接使用ES5的
Object.create()
方法生成一个新对象不过
bind
和Object.create()
都是ES5方法,部分IE浏览器(IE < 9)并不支持,Polyfill中不能用Object.create()
实现bind
,不过原理是一样的。第四版目前OK啦,代码如下:
模拟实现第五步
到这里其实已经差不多了,但有一个问题是调用
bind
的不是函数,这时候需要抛出异常。所以完整版模拟实现代码如下:
【进阶3-2期】思考题解
【进阶3-3期】思考题解
call
的模拟实现如下,那有没有什么问题呢?当然是有问题的,其实这里假设
context
对象本身没有fn
属性,这样肯定不行,我们必须保证fn
属性的唯一性。ES3下模拟实现
解决方法也很简单,首先判断
context
中是否存在属性fn
,如果存在那就随机生成一个属性fnxx
,然后循环查询context
对象中是否存在属性fnxx
。如果不存在则返回最终值。一种循环方案实现代码如下:
一种递归方案实现代码如下:
模拟实现完整代码如下:
ES6下模拟实现
ES6有一个新的基本类型
Symbol
,表示独一无二的值,用法如下。不能使用
new
命令,因为这是基本类型的值,不然会报错。模拟实现完整代码如下:
测试用例在这里:
扩展一下
有两种方案可以判断对象中是否存在某个属性。
in
操作符in
操作符会检查属性是否存在对象及其[[Prototype]]
原型链中。Object.hasOwnProperty(...)
方法hasOwnProperty(...)
只会检查属性是否存在对象中,不会向上检查其原型链。注意以下几点:
in
操作符可以检查容器内是否有某个值,实际上检查的是某个属性名是否存在。对于数组来说,4 in [2, 4, 6]
结果返回false
,因为[2, 4, 6]
这个数组中包含的属性名是0,1,2
,没有4
。Object.prototype
的委托来访问hasOwnProperty(...)
,但是对于一些特殊对象(Object.create(null)
创建)没有连接到Object.prototype
,这种情况必须使用Object.prototype.hasOwnProperty.call(obj, "a")
,显示绑定到obj
上。又是一个call
的用法。本期思考题
用 JS 实现一个无限累加的函数
add
,示例如下:参考
进阶系列目录
交流
进阶系列文章汇总如下,内有优质前端资料,觉得不错点个star。
我是木易杨,网易高级前端工程师,跟着我每周重点攻克一个前端面试重难点。接下来让我带你走进高级前端的世界,在进阶的路上,共勉!
The text was updated successfully, but these errors were encountered: