春天到了,又到了交配,啊 ,不是。。又到了找工做的季节。相信不少朋友都会被问到过这样的一个JS问题,如何实现call
| apply
| bind
,不少朋友只会用可是不会写,或者是死记硬背写法,等到面试官提问的时候,支支吾吾讲不清楚,今天我将教会你们彻底理解这个破题!es6
这是一个很方便,可是同时又容易出错的属性。面试
咱们只要记住4条规则就行了数组
这个时候this指向window对象浏览器
let x = 'window'; function test() { let x = 'fn'; console.log(x); } test(); // window //注意这里若是使用var的话,会是fn //由于var没有块级做用域,函数内var会至关于在外面var //也就是更改了window.x也就是this.x。
向上面这种简单的你们都能理解,看看这个容易搞错的app
var name = "zhangsan"; var obj = { name:"leelei", fn:function() { var x = function() { console.log(this.name) }; x(); } } obj.fn() // zhangsan
这个时候的this就指向这个对象函数
function test() { console.log(this.x); } var obj = {}; o.x = 1; o.m = test; o.m(); // 1
function Test() { this.x = 1; } var o = new Test(); console.log(o.x); // 1
apply(),call()是函数对象的一个方法,它的做用是改变函数的调用对象,它的第一个参数就表示改变后的调用这个函数的对象。所以,this指的就是第一个参数。学习
bind()和他们相似,可是它执行后返回的仍是一个函数,而不是执行后的值。this指的也是第一个参数。this
他的特性是把fn中的this指向第一个参数,当咱们使用的时候是这样的。prototype
它实现了把 sayName中的this指向了 obj,即this.nickName
=>obj.nickName
code
function sayName() { console.log(`my name is ${this.nickName}`); } let obj = {nickName:"leelei"} sayName.call(obj) //my name is leelei
咱们能够看看上面this的使用方法中的第二点,咱们若是把fn设置为context的一个属性
,是否是fn的this就会指向context了呢?
context.property = fn; let result = context.property(); delete context.property ; return result;
context
的一个属性result
context
对象,谁乐意干干净净进来,出去的时候带了一坨屎啊。call的用法是这样的:除了第一个参数之外,其余的参数全都是传给fn
那么借助es6语法咱们能够省下一大堆代码,大体代码以下
Function.prototype.mycall\= function(context,...args) { context.fn= this; //这里的this指向调用该方法的实例,也就是fn.call()中的fn let result = context.fn(...args); delete context.fn; return result; }
完事了吗?
固然没有,做为男人怎么能够那么快完事儿?
想一想knight
会怎么作?阿,不是,想一想call会怎么作。
function sayName() { console.log(`my name is ${this.nickName}`); } var sym = Symbol('halo') //ES6新增基础类型,若是不懂,没有瓜西! sayName.call(sym) // my name is undefined sayName.call('malegeji') // my name is undefined sayName.call(666) // my name is undefined sayName.call(true) // my name is undefined sayName.call(null) // my name is undefined sayName.call(undefined) //my name isundefined
undefined
表明什么呢?
你能够看看下面这个代码
//对一个对象访问它没有的属性值时会返回undefined var obj = {}; obj.malegeji //undefined
这个说明call内部,把咱们输入的基础类型都转成了对象,那么null和undefined也是如此吗?他们根本就没有本身的构造函数方法阿?那他们转成了什么?
是洋葱
,转成了洋葱
!
好吧,实际上是window
如何验证?咱们只要给个window的这个属性赋值看一下就知道啦
window.nickName = "leelei" function sayName() { console.log(\`my name is ${this.nickName}\`); } var sym = Symbol('halo') sayName.call(sym) // my name is undefined sayName.call('malegeji') // my name is undefined sayName.call(666) // my name is undefined sayName.call(true) // my name is undefined sayName.call(null) // my name is leelei sayName.call(undefined) //my name isleelei
哦豁,验证了咱们的想法~
搞清楚特性之后,咱们如今就能够写出一个和call一毛同样表现的mycall了~
Function.prototype.mycall = function(context,...args) { if (typeof this !== 'function') { throw new TypeError('not funciton') } if(context == null || context == undefined) { context = window }else{ context = Object(context); } context.fn = this; let result = context.fn(...args); delete context.fn; return result; };
apply和call其实大部分是同样的,他们的惟一区别是什么?
传参格式不同
fn.call(context,arg1,arg2,arg3,...) fn.apply(context,[arg1,arg2,arg3,...])
那么,咱们能够轻易地实现apply
Function.prototype.myapply = function(context,args) { if (typeof this !== 'function') { throw new TypeError('not funciton') } if(arguments.length>2){ throw new Error("Incorrect parameter length") } if(context == null || context == undefined){ context = window }else{ context = Object(context); } context.fn = this; let result = context.fn(...args); delete context.fn; return result; }
看这一部分以前,请先对构造函数
有一个比较清晰的了解,否则能够点赞
而后关掉网页了,固然也可再点个收藏
。
function sayName(age,sex) { console.log(`my name is ${this.nickName},I'm ${age} years old, ${sex}`); } let obj = { nickName: "leelei" } let bindFn = sayName.bind(obj) //注意:使用bind后返回的是一个函数 bindFn(); //my name is leelei,I'm undefined years old, undefined
哎呀,忘了传参数,怎么传呢?
第一种
let bindFn = sayName.bind(obj,18,'man') //注意:使用bind后返回的是一个函数 bindFn(); //my name is leelei,I'm 18years old,man
第二种
let bindFn = sayName.bind(obj,18) //注意:使用bind后返回的是一个函数 bindFn('man'); //my name is leelei,I'm 18years old,man
你能够把除了第一个参数之外的参数随意在绑定的时候传入,或者在执行的时候传入,这个也是一个函数柯里化
的过程。
柯里化,英语:Currying是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,而且返回接受余下的参数并且返回结果的新函数的技术。
由于bind返回的是一个函数,当咱们把这个函数看成构造函数
来使用,那又会怎样呢?
//为何我一用构造函数举例会下意识命名为foo | bar function Foo(age, sex) { this.blog = "http://www.leelei.info" console.log(this.nickName); console.log(age); console.log(sex); } Foo.prototype.habit = "play lol in Zu'an and kill somebody's mom"; let bindFn = Foo.bind({ nickName: "leelei" }, 18, "man"); let bindFnInstance = new bindFn(); // undefined 18 'man' console.log(bindFnInstance.blog); // http://www.leelei.info console.log(bindFnInstance.habit); //play lol in Zu'an and kill somebody's mom let bindFnInstance2 = bindFn(); //普通调用,由于不是new运算符因此没有返回 console.log(bindFnInstance2.habit); // Cannot read property 'habit' of undefined
聪明的盲生,你发现什么华点了吗?
个人nickName
怎么是undefined
阿,完了,全完了,我浏览器有问题,我先把谷歌卸了!
别急,实际上是由于当使用new操做符来构造绑定函数的时候,bind会忽略这个传入的第一个参数,为何?
由于构造函数Foo中的this会指向实例用于构造实例,(这个是new的特性,若是不明白能够百度一下),那么this指到实例bindFnInstance后就不能指到传入的第一个参数了,那么它的nickName就是bindFnInstance的nickName了,可是bindFnInstance说:”我他妈刚生成哪里来的nickName阿“,因此最终就没法访问了嗷。
好的,咱们来总结一下这几个特性嗷
返回函数
特性那颗太简单了嗷,铁子,干了奥里给!
Function.prototype.mybind = function(context) { return function() { return fn.call(context); }; };
参数的分步传入
Function.prototype.mybind = function(context, ...args) { return function(...args2) { return fn.call( context, ...args, ...args2); }; };
Function.prototype.mybind = function(context, ...args) { const fn = this; function fBound(...args2) { return fn.call(this instanceof fBound ? this : context, ...args, ...args2); }; fBound.prototype = Object.create(this.prototype); return fBound; };
this instanceof fBound? this : context
是干什么的?你可能看过如何判断数组代码,arr instanceof Array
,是否是感受很像?有么有感受了?
这个instanceof
能够判断 右边这个构造函数是否出如今左边这个对象的原型链上。
按照写法,咱们返回了fBound
- 若是使用普通调用,那么咱们这个this会指向window嘛(fBound中的this属于第一部分提到的this的第一种用法),window和fBound有个毛关系?因此返回context做为fn的第一个参数。
- 若是使用的是new,那么这个this指向的就是新的实例,新的实例的原型链确定有它的构造函数fBound阿,那么就传入this,也就是实例自己,而忽略context,其余参数不变。
fBound.prototype = Object.create(this.prototype)
是干什么的?当咱们使用构造函数的时候,构造函数原型上的属性,实例也可访问,也就是这里所表现的。
Foo.prototype.habit = "play lol in Zu'an and kill somebody's mom"; console.log(bindFnInstance.habit); //play lol in Zu'an and kill somebody's mom
可是咱们返回的是fBound,fBound哪里来的prototype.habit阿,因此咱们给他整上!
那能不能直接执行fBound.prototype =fn.prototype
,将原函数的 prototype 赋值给 fBound 呢?
很明显这样的操做把 fBound 和 原函数的 prototype 强关联起来了,若是fBound 函数的 prototype改动 将会影响到原函数的 prototype,因此能够经过 fBound.prototype = Object.create(fn.prototype)
,以原函数的 prototype为模板,生成一个新的实例对象,并赋值给fBound.prototype。
固然没有!
还有一个须要注意的点
当咱们执行到 fBound.prototype = Object.create(fn.prototype)
时,若是fn.prototype是undefined可咋整,什么状况下会出现呢?
当咱们直接调用 Foo.prototype.bind 时候会出现,而且bong
的一声报了个大错!
typeof Function.prototype === "function" //true 因此咱们像前面call,apply那样的判断也限制不了 同时 Function.prototype.prototype // undefined
固然。。。没有!
由于 Object.create() 和 bind 都是 ES5 规范提出的,若是不支持 bind, 那么bind 的 polyfill 里面天然不支持 Object.create()。因此咱们应该换个方法来实现,通常面试官到上面一步就足了。
最终究极终稿!
Function.prototype.mybind = function(context, ...args) { if (typeof this !== "function") { throw new TypeError("not funciton"); } const fn = this; const fNop = function () {}; function fBound(...args2) { return fn.call(this instanceof fBound ? this : context, ...args, ...args2); }; if(fn.prototype){ fNop.prototype =fn.prototype; } fBound.prototype = new fNop(); return fBound; };
总的来讲call,apply,bind这三个方法涉及到了js的诸多方法,若是可以彻底理解的话,对于学习js会有很大帮助嗷~
若是有错误,请在评论区中指出,很是感谢!
顺便打个广告 leelei的我的博客最后祝你们拿到心仪的offer