JavaScript(call、apply)|青训营笔记

126 阅读3分钟

JavaScript(call、apply)|青训营笔记

这是我参与「第四届青训营 」笔记创作活动的的第7天

call、apply、bind 区别

共同点

  • 功能一致,可以改变this指向
  • 语法都是函数

区别

  • 1.call() apply() 可以立即执行。 bind不会立即执行,bind返回的是一个函数,不会立即执行
  • 2.参数不同: apply 第二个参数是数组。 call和bind有多个参数,需要一个一个写

手写实现call方法

首先要明白call、apply、bind 哪里来的?每个JavaScript函数其实都是Function对象,而function对象是构造函数, 构造函数是有原型对象的,也就是function.prototype
我们想要实现,就需要在prototype中添加新的和call一样的属性(方法)

function.prototype.png

call()

使用call进行this绑定,就相当于做了this的隐式绑定,在对象里添加了调用this的方法
如下code1 到 code2 的变化,当函数go引用有上下文的时候,就会把函数go调用中的this
绑定到这个上下的对象 MyObj 中了

// code1
function go() {
  console.log('练习两年半的'+this.name);
}

var MyObj = {
  name: '个人练习生',
}

go.call( MyObj );

/********************* */

// code2
var MyObj = {
  name: '个人练习生',
  go: function() {
    console.log('练习两年半的'+this.name);
  }
}
MyObj.go();

现在开始在新的call函数中完成这个效果(往原型对象中添加新的方法)

function go() {
  console.log('练习两年半的'+this.name);
}
var MyObj = {
  name: '个人练习生'
}
Function.prototype.newCall = function(obj) {
  console.log(this);
}
go.newCall(MyObj);

上述代码的输出是?
P2.png
返回的是go函数,而不是MyObj对象,因为在执行newCall时,遇到的this是在go中执行的,因此返回的是 go 函数 现在如果在newCall里 给obj添加一个函数,赋值为go,并且执行一遍,是不是就是把this绑定给传入的obj了呢?

function go() {
  console.log('练习两年半的'+this.name);
}
var MyObj = {
  name: '个人练习生'
}
Function.prototype.newCall = function(obj) {
  // 改动的代码 start
  obj.f = this;  // 给obj重新添加了一个方法 赋值为 this(go()方法)
  obj.f();        // 执行调用
  delete obj.p;   // 不需要真的添加,执行完毕后删除
  // 改动的代码 end
}
go.newCall(MyObj);

P3.png

call的其他参数的实现,使用解构数组的方法

function go(a,b,c,d) {
  console.log('练习两年半的 '+this.name);
  console.log(a,b,c,d);
}
var MyObj = {
  name: '个人练习生'
}
Function.prototype.newCall = function(obj) {
  obj.f = this;  // 给obj重新添加了一个方法 赋值为 this(go()方法)
  var newArr = [];  // 创建一个新数组来存放 arguments
  for(var i=1;i<arguments.length;i++) {  // 从1开始是因为第一个参数是 this
    newArr.push(arguments[i]);
  }
  console.log(newArr);
  obj.f(...newArr);        // 执行调用
  delete obj.p;   // 不需要真的添加,执行完毕后删除
}
go.newCall(MyObj,'唱','跳','rap','篮球');

P4.png

看起来已经非常完美了,但是,距离我们实现自己的call函数还有一个的问题,如果我们第一个参数传入null,就会报错,但是原生的call则不会

P5.png

// 添加如下代码
var obj = obj || windows;

最终版本

使用es6语法简化后,并给函数一个返回值

function go(a,b,c,d) {
  return {
    name: this.name,
    a:a,
    b:b,
    c:c,
    d:d
  }
}
const MyObj = {
name: '个人练习生'
}
Function.prototype.newCall = function(obj,...arr) {
  let newObj = obj || windows;
  newObj.f = this;  // 给obj重新添加了一个方法 赋值为 this(go()方法)
  const result =  newObj.f(...arr);        // 执行调用
  delete newObj.f;   // 不需要真的添加,执行完毕后删除
  return result;
}
const a = go.newCall(MyObj,'唱','跳','rap','篮球');
console.log(a);

last.png