JS进阶第一篇:手写call apply bind 您所在的位置:网站首页 js中call函数 JS进阶第一篇:手写call apply bind

JS进阶第一篇:手写call apply bind

#JS进阶第一篇:手写call apply bind| 来源: 网络整理| 查看: 265

文章目录 手写call apply bind深入理解 call 方法手写call手写apply手写bind

手写call apply bind 深入理解 call 方法

call 理解了,apply和bind就都迎刃而解了,他们都是大同小异。在此对call和apply不做过多的定义性解释,先来看下调用了call后谁是那个被执行的方法,直接代码示例:

function fn1 () { console.log(1); }; function fn2 () { console.log(2); }; fn1.call(fn2);//1

执行fn1.call(fn2);控制台会打印1,这里可以说明fn1调用call后被执行的方法还是fn1。一定要弄清楚谁是这个被执行的方法,就是调用call的函数,而fn2现在的身份是替代window作为fn1的直接调用者,这是理解call和apply的关键,也可以运行下fn2.call(fn1); 再来个代码示例:

var obj1 = { num : 20, fn : function(n){ console.log(this.num+n); } }; var obj2 = { num : 15, fn : function(n){ console.log(this.num-n); } }; obj1.fn.call(obj2,10);//25

执行obj1.fn.call(obj2,10);控制台会打印25,call在此的作用其实很简单,就是在执行obj1.fn的时候把这个fn的直接调用者由obj1变为obj2,obj1.fn(n)内部的this经过call的作用指向了obj2,所以this.num就是obj2.num,10作为执行obj1.fn时传入的参数,obj2.num是15,因此打印出的值是15+10=25。 所以我们可以这样理解:call的作用是改变了那个被执行的方法(也就是调用call的那个方法)的直接调用者!而这个被执行的方法内部的this也会重新指向那个新的调用者,就是call方法所接收的第一个obj参数。还有两个特殊情况就是当这个obj参数为null或者undefined的时候,this会指向window。

手写call Function.prototype.myCall = function (context) { // 先判断调用myCall是不是一个函数 // 这里的this就是调用myCall的 if (typeof this !== 'function') { throw new TypeError("Not a Function") } // 不传参数默认为window context = context || window // 保存this context.fn = this // 保存参数 let args = Array.from(arguments).slice(1) //Array.from 把伪数组对象转为数组,然后调用 slice 方法,去掉第一个参数 // 调用函数 let result = context.fn(...args) delete context.fn return result } 手写apply Function.prototype.myApply = function (context) { // 判断this是不是函数 if (typeof this !== "function") { throw new TypeError("Not a Function") } let result // 默认是window context = context || window // 保存this context.fn = this // 是否传参 if (arguments[1]) { result = context.fn(...arguments[1]) } else { result = context.fn() } delete context.fn return result } 手写bind

在实现手写bind方法的过程中,看了许多篇文章,答案给的都很统一,准确,但是不知其所以然,所以我们就好好剖析一下bind方法的实现过程。

我们先看一下bind函数做了什么:

bind() 方法会创建一个新函数。当这个新函数被调用时,bind() 的第一个参数将作为它运行时的 this,之后的一序列参数将会在传递的实参前传入作为它的参数。

读到这里我们就发现,他和 apply , call 是不是很像,所以这里指定 this 功能,就可以借助 apply 去实现:

Function.prototype.myBind = function (context) { // 这里的 this/self 指的是需要进行绑定的函数本身,比如用例中的 man const self = this; // 获取 myBind 函数从第二个参数到最后一个参数(第一个参数是 context) // 这里产生了闭包 const args = Array.from(arguments).slice(1) return function () { // 这个时候的 arguments 是指 myBind 返回的函数传入的参数 const bindArgs = Array.from(arguments) // 合并 return self.apply(context, args.concat(bindArgs)); }; };

大家对这段代码应该都能看懂,实现原理和手写 call , apply 都很像,因为 bind 可以通过返回的函数传参,所以在 return 里面获取的 bindArgs 就是这个意思,然后最后通过 concat 把原来的参数和后来传进来的参数进行数组合并。

我们来看一下结果:

const person = { name: 'zyj' } function man(age) { console.log(this.name); console.log(age) } const test = man.myBind(person) test(18)//zyj 18

现在重点来了,bind 区别于 call 和 apply 的地方在于它可以返回一个函数,然后把这个函数当作构造函数通过 new 操作符来创建对象。

我们来试一下:

const person = { name: 'zyj' } function man(age) { console.log(this.name); console.log(age) } const test = man.myBind(person) const newTest = new test(18) // zyj 18

这是用的我们上面写的 myBind 函数是这个结果,那原生 bind 呢?

const person = { name: 'zyj' } function man(age) { console.log(this.name); console.log(age) } const test = man.bind(person) const newTest = new test(18) // undefined 18 由上述代码可见,使用原生 bind 生成绑定函数后,通过 new 操作符调用该函数时,this.name 是一个 undefined,这其实很好理解,因为我们 new 了一个新的实例,那么构造函数里的 this 肯定指向的就是实例,而我们的代码逻辑中指向的始终都是 context ,也就是传进去的参数。

所以现在我们要加个判断逻辑:

Function.prototype.myBind = function (context) { // 这里的 this/self 指的是需要进行绑定的函数本身,比如用例中的 man const self = this; // 获取 myBind 函数从第二个参数到最后一个参数(第一个参数是 context) // 这里产生了闭包 const args = Array.from(arguments).slice(1) const theBind = function () { const bindArgs = Array.from(arguments); // 当绑定函数作为构造函数时,其内部的 this 应该指向实例,此时需要更改绑定函数的 this 为实例 // 当作为普通函数时,将绑定函数的 this 指向 context 即可 // this instanceof fBound 的 this 就是绑定函数的调用者 return self.apply( this instanceof theBind ? this : context, args.concat(bindArgs) ); }; return theBind; };

现在这个效果我们也实现了,那我们的 myBind 函数就和其他的原生 bind 一样了吗?来看下面的代码:

const person = { name: 'zyj' } function man(age) { console.log(this.name); console.log(age) } man.prototype.sayHi = function() { console.log('hello') } const test = man.myBind(person) const newTest = new test(18) // undefined 18 newTest.sayHi()

如果 newTest 是我们 new 出来的 man 实例,那根据原型链的知识,定义在man的原型对象上的方法肯定会被继承下来,所以我们通过 newTest.sayHi 调用能正常输出 hello 么?

alt

该版代码的改进思路在于,将返回的绑定函数的原型对象的 proto 属性,修改为原函数的原型对象。便可满足原有的继承关系。

Function.prototype.myBind = function (context) { // 这里的 this/self 指的是需要进行绑定的函数本身,比如用例中的 man const self = this; // 获取 myBind 函数从第二个参数到最后一个参数(第一个参数是 context) // 这里产生了闭包 const args = Array.from(arguments).slice(1); const theBind = function () { const bindArgs = Array.from(arguments); // 当绑定函数作为构造函数时,其内部的 this 应该指向实例,此时需要更改绑定函数的 this 为实例 // 当作为普通函数时,将绑定函数的 this 指向 context 即可 // this instanceof fBound 的 this 就是绑定函数的调用者 return self.apply( this instanceof theBind ? this : context, args.concat(bindArgs) ); }; theBind.prototype = Object.create(self.prototype) return theBind; };


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有