知识拓展
polyfill:抽象理解为把坑填平用到的东西,可以理解为对代码的一种兼容处理(也就是检测一个属性是否存在,然后对其进行处理的行为),比如下面的代码就是polyfill
if (!Function.prototype.bind) {
Function.prototype.bind = function () {}
}
实现bind
1. 接受this参数
单元测试代码如下:
const fn1 = function() {
return this
}
const newFn1 = fn1.bind2({ name: 'lifa' })
console.assert(newFn1().name === 'lifa')
上面的代码就是我们声明一个fn1函数通过调用bind2传入第一个参数,我们断言它的this就是我们传入的{name:'lifa'}
思路:我们在bind2里调用fn1然后通过call将我们的第一个参数作为fn1的this,那么怎么能在不作为参数传入的情况下在bind2里调用fn1那?fn1.bind2可以看成是fn1.bind2.call(fn1),所以bind2里的this就是fn1。
代码实现
function bind(asThis, p1, p2) {
const fn = this
return function() {
return fn.call(asThis)
}
}
module.exports = bind
2. 接受this. p1, p2参数
单元测试
const fn2 = function(p1, p2) {
return [this, p1, p2]
}
const newFn2 = fn2.bind2({ name: 'lifa' }, 123, 456)
console.assert(newFn2()[0].name === 'lifa')
console.assert(newFn2()[1] === 123)
console.assert(newFn2()[2] === 456)
代码实现
function bind(asThis, ...arguments) {
const fn = this
return function() {
return fn.call(asThis, ...arguments)
}
}
3.先传this,再传其他参数
单元测试代码
const anotherFn2 = fn2.bind2({ name: 'lifa' })
console.assert(anotherFn2(222, 333)[0].name === 'lifa')
console.assert(anotherFn2(222, 333)[1] === 222)
console.assert(anotherFn2(222, 333)[2] === 333)
代码实现
function bind(asThis, ...args) {
const fn = this
return function(...args2) {
return fn.call(asThis, ...args, ...args2)
}
}
4.问题:上面的代码是es6的如果再ie里可能不兼容,所以我们需要对其兼容
1). 把const变成var
2). 如果不是函数调用bind抛出错误
3).替代扩展运算符
var slice = Array.prototype.slice
function bind(asThis) {
// 得到除第一个参数之外的所有参数
// 因为arguments不是一个数组,它是一个伪数组,所以不能用slice
// 可以使用slice.call 从arguments的第1位开始截取
var args = slice.call(arguments, 1)
var fn = this
if (typeof fn !== 'function') {
throw new Error('bind 必须调用在函数身上')
}
return function() {
var args2 = slice.call(arguments, 0)
// 这里因为我们拿到的参数是数组没法通过扩展运算拿到每一项
// 所以将call换成了apply然后把两次参数的数组结合成一个数组
return fn.apply(asThis, args.concat(args2))
}
}
5. 支持new
单元测试
function test5(message) {
console.log(message)
Function.prototype.bind2 = bind
const fn = function(p1, p2) {
this.p1 = p1
this.p2 = p2
}
const fn2 = fn.bind2(undefined, 'x', 'y')
const object = new fn2()
console.assert(object.p1 === 'x')
console.assert(object.p2 === 'y')
}
如果我们的bind支持new的话那么我们的object.p1一定会等于‘x',object.p2一定会等于'y'
而且我们需要知道new的过程中都做了什么,它会做以下几步
var temp = {}
this = temp
this.__proto__= fn.prototype
fn.call(this,'x','y')
return this
在现在的代码基础上我们的测试报错,打印我们的object发现是一个空对象
5.1.问题分析
function _bind(asThis, ...args) {
const fn = this
return function(...args2) {
return fn.call(asThis, ...args, ...args2)
}
}
拿我们上面es6版的bind来分析,当我们调用new fn()的时候实际上fn是我们上面的function(...args2) {return fn.call(asThis, ...args, ...args2)}
,就相当于
new function(...args2) {
// 1.var temp = {}
// 2.this = temp
// 3.this.p1 = 'x'
// 4.this.p2 = 'y'
// 5.return this
return fn.call(asThis, ...args, ...args2)
}
上面代码里当我们执行new的时候帮我们执行上面1-4的注释代码,然后默认会执行5帮我们return这个this(默认的意思就是如果我们没写return的情况下new就会帮我们return这个this,如果写了就return我们自己写的里面的this),所以上面代码实际执行完4后就会执行我们的return fn.call(asThis, ...args, ...args2)
,也就是调用我们的fn,fn里面有this,它的this就是我们的asThis,而我们的asThis传的是undefined,所以我们的p1,p2都放到window里去了
5.2.解决方法
我们需要区分new的时候用自己的this,不new的时候用我们传的this,怎么区函数被调用的时候有没有用到new哪?
因为new的过程中会做this.proto = fn.prototype,所以我们根据这个来判断就可以
return function resultFn(...args2) {
return fn.call(this.__proto__ === resultFn.prototype ? this : asThis, ...args, ...args2)
}
5.3. 支持构造函数.prototype里的属性和方法
单元测试
function test6(message) {
console.log(message)
Function.prototype.bind2 = bind
const fn = function(p1, p2) {
this.p1 = p1
this.p2 = p2
}
fn.prototype.say = function() {
console.log('say')
}
const fn2 = fn.bind2(undefined, 'x', 'y')
const object = new fn2()
console.assert(object.p1 === 'x')
console.assert(object.p2 === 'y')
console.assert(object.__proto__ === fn.prototype)
console.assert(typeof object.say === 'function')
}
基于之前的代码我们的resultFn里面new的时候已经帮我们做了this.proto = resultFn.prototype,而实际上我们是要我们的this.__proto = fn.prototype,所以我们只需要将resultFn.prototype=fn.prototype
即可
function _bind(asThis, ...args) {
const fn = this
function resultFn(...args2) {
// var temp = {}
// this = temp
// this.__proto__ = resultFn.prototype
return fn.call(this.__proto__ === resultFn.prototype ? this : asThis, ...args, ...args2)
}
resultFn.prototype = fn.prototype
return resultFn
}
5.4.使用instanceof替换proto
因为proto不是规范属性,所以我们不能直接用,那如果不用proto我们怎么判断一个函数是不是通过new调用的那,使用instanceof,只要是用new调用的那么 实例 instanceof 构造函数
return fn.call(this instanceof resultFn ? this : asThis, ...args, ...args2)
完整代码:
function _bind(asThis, ...args) {
const fn = this
function resultFn(...args2) {
// var temp = {}
// this = temp
// this.__proto__ = resultFn.prototype
return fn.call(this instanceof resultFn ? this : asThis, ...args, ...args2)
}
resultFn.prototype = fn.prototype
return resultFn
}
var slice = Array.prototype.slice
function bind(asThis) {
var fn = this
var args = slice.call(arguments, 1)
if (typeof fn !== 'function') {
throw new Error('bind的类型必须是函数')
}
function resultFn() {
var args2 = slice.call(arguments, 0)
return fn.apply(this instanceof resultFn ? this : asThis, args.concat(args2))
}
resultFn.prototype = fn.prototype
return resultFn
}