当前位置: 首页 > 科技观察

手写绑定:应对新的_0

时间:2023-03-20 14:30:41 科技观察

大家好,我是前端西瓜哥。之前写过一篇关于js中bind方法实现的文章,给出了实现:Function.prototype.myBind=function(thisArg,...prefixArgs){constfn=this;returnfunction(...args){returnfn.call(thisArg,...prefixArgs,...args);但是它不处理new创建的实例。因为很少遇到对bind返回的函数进行new操作的场景,所以没有考虑这种特殊情况。但它仍然会参与面试。下面来实现兼容new操作的bind写法,顺便了解一下new操作符。因为有一定的语境,所以在看这篇文章之前,建议先阅读上一篇:《??前端面试题:手写 bind??》。new让我们先了解一下new运算符。new用于通过函数创建对象实例,在很多语言中都可以看到。JS函数,除了普通的函数,比如:functionsum(a,b){returna+b;}也可以是构造函数,构造的时候在前面加一个new即可:functionPerson(name,age){this.name=名称;this.age=age;}constperson=newPerson('前端西瓜',100)//Person{name:'前端西瓜',age:100}new创建一个新对象,执行以下操作:创建空对象{}。空对象的原型属性__proto__指向构造函数的原型对象Person.prototype。函数中的this设置为这个空对象。如果函数没有返回对象,则返回thisthis,否则返回this对象。如何判断一个函数是否被new操作符调用?答案是通过instanceof来判断this是否是当前函数的实例,即thisinstanceofFn为true,说明该实例是通过new构造的。让我们看一个例子:functionPerson(){if(thisinstanceofPerson){console.log('constructaninstancewithnew');}else{console.log('normalcall')}}Person()//output:普通调用newPerson()//Output:在Vuejs的源码中,你会看到如下代码通过new构建实例,这里也使用了这种技术。functionVue(options){if(__DEV__&&!(thisinstanceofVue)){warn('Vue是一个构造函数,应该用`new`关键字调用')}this._init(options)}你在开发中环境如果不通过new使用vue对象,会在控制台提示通过new调用vue。new和bind如果我们new是Function.prototype.bind返回的新函数会怎样?functionPerson(name,age){this.name=name;this.age=age;}constBoundPerson=Person.bind(null,'前端西瓜');constboundPerson=newBoundPerson(100);//Person{name:'前端西瓜哥',age:100}boundPerson.__proto__===Person.prototype//true结果相当于直接进入新的原函数。不同之处在于参数预设仍然是可能的。可以看到调用bind时,构造函数的第一个参数提前设置为'前端西瓜哥'。bind的完整实现如下:Function.prototype.myBind=function(thisArg,...prefixArgs){constfn=this;constboundFn=function(...args){//通过new使用当前函数if(thisinstanceofboundFn){returnnewfn(...prefixArgs,...args);}//普通方法调用当前函数returnfn.call(thisArg,...prefixArgs,...args);}boundFn.prototype=fn.prototype;returnboundFn;}这里我是通过这个instanceofboundFn来判断是否使用了new,如果是则直接new原函数并返回,记得带上bind预置的参数。其他保持原样(详见上文《前端面试题:手写 bind》)。boundFn.prototype=fn.prototype;这个可以写也可以不写,只要让bind返回的新函数的原型指向原函数的原型即可。如果是nativebind返回的函数,是没有protoype属性的,可以认为是一个特殊的函数,但是我们实现的bind返回的是普通函数,所以不能完全模拟。如果你追求完美实现,可以研究一下Function.prototype.bind标准:https://tc39.es/ecma262/#sec-function.prototype.bind。再看看知名的core.js库中bind的实现:https://github.com/zloirock/core-js/blob/cafe9ecf2b384385f8b8d1da0047e44586fff2dc/packages/core-js/internals/function-bind.js#L23-L33。核心实现是://`Function.prototype.bind`方法实现//https://tc39.es/ecma262/#sec-function.prototype.bindmodule.exports=Function.bind||functionbind(that/*,...args*/){varF=aCallable(this);varPrototype=F.prototype;varpartArgs=arraySlice(arguments,1);varboundFunction=functionbound(/*args...*/){varargs=concat(partArgs,arraySlice(arguments));返回boundFunction的这个实例?construct(F,args.length,args):F.apply(that,args);};if(isObject(Prototype))boundFunction.prototype=Prototype;返回绑定函数;};这里说的比较详细:这里判断这个是否是函数类型,如果不是函数会报错。F.prototype需要是一个对象或者一个函数,才能赋值给一个新的函数。为了与ES5兼容,使用了正常的函数和参数。末尾手写的bind现在想起来不太容易,需要掌握很多知识点:bind的详细用法:包括改变this的性能,预设参数,new。闭包的使用:保存一些私有变量。使用原型链(thisinstanceofboundFn)判断是否通过new调用当前函数。使用call在执行时更改函数的this指针。