很多人都知道这三种方法怎用,但是都不知道他们的原理
call
- 定义
call() 方法调用一个函数,其具有一个指定的this值和分别地提供的参数(参数的列表)。复制代码
let name='憨蛋';let person={ name:'romin'}function say(){ console.log('my name is'+this.name)}say.call(person);// my name is romin复制代码
cal
l 改变了say
函数的this
指向,同时say
函数执行。
那么我们就来实现它
- 1、改变this指向,直接把
say
函数放进person
对象中即可。 - 2、函数执行,调用
person
中的say
函数
let person={ name:'romin', say:function(){ console.log(this.name) }}person.say();复制代码
由于上面的方法改变了 person
对象的结构,需要需要多余的 say
函数删除掉,要执行 delete person.say
的操作。
这时候,我们已经明确了该做的事情:
1、把待执行的函数(call
前面的函数)放入指定的对象(call
的第一个参数)中;
2、执行该函数;
3、删除对象中添加的函数;
Function.prototype.call = function(context){ // 第一步 //这里的this就是代调用的函数 context.fn = this; // 第二步 context.fn(); // 第三步 delete context.fn;}复制代码
call
中的第一个参数也可能是null
,也可能是字符串,我们需要对 context 进行处理,否则 字符串上没法挂载 函数 fn
。
// 对context 做处理context = context?Object(context):window;复制代码
以上面的为例,call
中可能会有多个参数,除了context
,剩余的参数都要交给 say
来处理的。
let name='憨蛋';let person={ name:'romin'}function say(animal,age){ console.log(animal) console.log(age) console.log('my name is '+this.name+',我属'+animal);}say.call(person,'?',15);// my name is romin,我属?复制代码
call
里的参数该怎么交给 say
,并且让它执行呢?使用 arguments
的话,它是类数组,和 call
的要求不一致,call
要求 一个个传参的。 拼出一个参数字符串来。
let args = [];for(let i=1;i
完整的实现:
Function.prototype.call = function(context){ // 对context 做处理 context = context?Object(context):window; // 第一步,挂载函数 //这里的this就是代调用的函数 context.fn = this; // 第二步,准备参数,然后让函数执行 let args = []; for(let i=1;i
apply
- 定义
apply() 方法调用一个具有给定this值的函数,以及作为一个数组(或类似数组对象)提供的参数。复制代码
apply
和 call
类似,只是传参的不同而已,可以直接拿上面的代码进行改造
Function.prototype.apply = function(context,arr){ // 对context 做处理 context = context?Object(context):window; // 第一步,挂载函数 //这里的this就是代调用的函数 context.fn = this; let result; if(!arr){ result = context.fn(); }else{ let args = []; for(let i=0;i
bind
- 定义:
`bind()`方法创建一个新的函数,在调用时设置`this`关键字为提供的值。并在调用新函数时,将给定参数列表作为原函数的参数序列的前若干项。复制代码
特点:改变 this
指向,同时返回一个函数(高阶函数),可以传入参数;
let name='憨蛋';let person={ name:'romin'}function say(animal,age){ console.log(animal) console.log(age) console.log('my name is '+this.name+',我属'+animal);}let rominsay = say.bind(person,'?');rominsay(15);复制代码
它的模拟实现,如果不带参数列表:
Function.prototype.bind = function(context){ let that = this; return function(){ // 同样要注意可能有返回值 return that.apply(context); }}复制代码
第二版,如果有参数:
Function.prototype.bind = function(context){ let that = this; // 取到参数列表 let bindArgs = Array.prototype.slice.call(arguments,1); return function(){ // 取到调用时候的参数列表 let args = Array.prototype.slice.call(arguments); // 同样要注意可能有返回值 return that.apply(context,bindArgs.concat(args)); }}复制代码
接下来请看,下面的例子使用原生的 bind
方法:
let person={ name:'romin', gender:'男',}function Say(name,age){ this.habit = '上班划水'; console.log(this.gender); console.log(name); console.log(age);}Say.prototype.friend = '憨蛋';let RominSay = Say.bind(person,'romin');let say = new RominSay('15');console.log(say.friend) // 憨蛋console.log(say.gender);// undefinedconsole.log(say.habit)// 上班划水复制代码
如果使用自己实现的方法,那么结果是
console.log(say.friend) // undefinedconsole.log(say.gender);// undefinedconsole.log(say.habit)// undefined复制代码
那么就会有两个疑惑:
- 1、原生的方法中
gender
失效了 - 2、我自己写的
friend
属性没有继承成功; - 3、我自己写的
habit
也没有取到;
但是请注意下面的一个问题:
如果被绑定的函数被new ,那么 返回的函数中的this 是当前函数的实例复制代码
套用上面的话,RominSay
被 new
出了一个 say
, 那么 this
就是 当前 RominSay
的实例say
(而不应该是 person
了),那么就能通过原型链找到 friend
属性,
对上面的实现方法进行改造
Function.prototype.bind = function(context){ let that = this; // 取到参数列表 let bindArgs = Array.prototype.slice.call(arguments,1); function newFun(){ // 取到调用时候的参数列表 let args = Array.prototype.slice.call(arguments); //当newFun作为构造函数时,this 指向实例,如果不是,this还指向 context return that.apply(this instanceof newFun ?this:context,bindArgs.concat(args)); } // 修改返回函数的 prototype 为绑定函数的 prototype,实例就可以继承绑定函数的原型中的值 newFun.prototype = this.prototype; return newFun;}复制代码