理解javascript中call/apply的使用和模拟实现

Posted 半夜盗贼

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了理解javascript中call/apply的使用和模拟实现相关的知识,希望对你有一定的参考价值。

call/apply 的作用
call() 方法调用一个函数, 其具有一个指定的this值和分别地提供的参数.
注意:该方法的作用和 apply() 方法类似,只有一个区别,就是call()方法接受的是若干个参数的列表,而apply()方法接受的是一个包含多个参数的数组。
function sayName(age) {
  // this.name = "person"; (1)
     console.log(this.name,age);
}
const student = {
    name: "student"
};
sayName.call(student,12); //person 12 (1)
sayName.call(student,13); //student 13
// call的作用: call 改变了 this 的指向,指向到 student
//(1)假如函数内部有相同属性情况,引用函数内部的属性
总结来说,call总共做了三件事,给对象加了一个函数,给函数传参,然后执行函数。下面我们对call进行模拟
下面我们先来模拟实现函数调用的this指向和函数的执行,怎么才能让函数的上下文指向调用对象呢,很简单我们直接把函数当作对象里面的一个方法即可,如
const student = {
  name: "student”,  
  sayName:function(age){
    console.log(this.name,age);
  }
};
student.sayName(12); //person 12 (1)
我们不能让函数一直在对象里面占用内存,所以在调用之后需要删除,这样我们就可以封装一个简化版的call
// 第一版
Function.prototype.callSub = function(context) {
  context.fn = this;
  context.fn();
  delete context.fn;
};
sayName.callSub(student); //student
这里我们基本上完成一个可用的call,但是最重要的一步传参还没有完成。我们可以通过Arguments 对象取值,取出第二个到最后一个参数,然后放到一个数组里,传给函数调用。
// 第二版
Function.prototype.callSub = function(context) {
  context.fn = this;
  let args = [];
  for (var i = 1; i < arguments.length; i++) { // 需要从第二个参数开始
    args.push(arguments[i]);
  }
  var result = context.fn(...args);
  delete context.fn;
  return result;
};
function sayName(age, name) {
  return {
    age: age,
    name: name
  };
}
const student = {
   name: "小红"
};
console.log(sayName.callSub(student, 12, "小明")); //{age: 12, name: "小明"}
在参数传值这里我们利用了es6的解构赋值功能实现数组到参数的转化,但是call/apply是在es5中加入的,所以我们不能用es6的语法实现,传参的工作。这里我们利用eval()函数拼接一个可执行的js字符串脚本。
还有一点需要注意的是,指定的this值并不一定是该函数执行时真正的this值,如果这个函数处于non-strict mode,则指定为null和undefined的this值会自动指向全局对象(浏览器中就是window对象),同时值为原始值(数字,字符串,布尔值)的this会指向该原始值的自动包装对象。
// 最终版
Function.prototype.callSub = function(context) {
  var context = context || window; // 当context为null 的时候指向window
  context.fn = this;
  let args = [];
  for (var i = 1; i < arguments.length; i++) { // 需要从第二个参数开始
    args.push("arguments[" + i + "]");
  }
  var result = eval("context.fn(" + args + ")");
  delete context.fn;
  return result;
};
function sayName(age, name) {
  return {   
    age: age,
    name: name,
    gender:this.gender
  };
}
const student = {
    name: "小红",
    gender:"woman"
};
console.log(sayName.callSub(student, 12, "小明")); //{age: 12, name: "小明", gender: "woman”}
 
function sayHello() {
  console.log("hello");
}
sayHello.callSub(); // hello
 
call和apply的区别就是传参的方式不一样
 
apply的模拟
// apply接受的参数是一个数组
Function.prototype.applySub = function(context, arr) {
  var context = context || window;
  context.fn = this;
  var result;
  if (!arr) {
    result = context.fn();
  } else {
    var args = [];
    for (var i = 0; i < arr.length; i++) {
      args.push("arr[" + i + "]");
    }
    result = eval("context.fn(" + args + ")");
  }
  delete context.fn;
  return result;
};
 
function sayName(age, name) {
  return {
    age: age,
    name: name,
    gender: this.gender
  };
}
const student = {
  name: "小红",
  gender: "woman"
};
console.log(sayName.applySub(student, [12, "小明"])); //{age: 12, name: "小明",gender:"woman"}
function sayHello() {
  console.log("hello");
}
sayHello.applySub(); // hello
 

以上是关于理解javascript中call/apply的使用和模拟实现的主要内容,如果未能解决你的问题,请参考以下文章

理解 JavaScript call()/apply()/bind()

javascript 中的apply call 的理解

javascript中call()apply()bind()的用法终于理解

javascript中call()apply()bind()的用法终于理解

理解JavaScript的caller,callee,call,apply

学习JavaScript之this,call,apply