JavaScript中call,apply,bind的区别与实现

作者:XerDemo 时间:2024-04-22 12:51:26 

区别

  • call、apply、bind相同点:都是改变this的指向,传入的第一个参数都是绑定this的指向,在非严格模式中,如果第一个参数是nul或者undefined,会把全局对象(浏览器是window)作为this的值,要注意的是,在严格模式中,null 就是 null,undefined 就是 undefined

  • call和apply唯一的区别是:call传入的是参数列表,apply传入的是数组,也可以是类数组

  • bind和call、apply的区别: bind返回的是一个改变了this指向的函数,便于稍后调用,不像call和apply会立即调用;bind和call很像,传入的也是参数列表,但是可以多次传入,不需要像call,一次传入

  • 值得注意:当 bind 返回的函数 使用new作为构造函数时,绑定的 this 值会失效,this指向实例对象,但传入的参数依然生效 (new调用的优先级 > bind调用)

call实现

对象context想调用一个它没有的方法f 怎么办呢?f.call(context) 通过call来借用方法f ,怎么做到的呢?

  • 对象context添加f方法

  • 对象context执行f方法

Function.prototype.myCall = function(context, ...arg) {
   // 如果第一个参数传入的是undefined和null,context为window对象
   context = context || window;  

// 为context对象添加函数bar
   context.fn = this;   // this:bar,this指向调用myCall的bar  

// context对象执行函数bar,并返回结果
   return  context.fn(...arg);
}

// 测试一下
var value = 2;

var obj = {
   value: 1
}
function bar(name, age) {
   console.log(this.value);
   return {
       value: this.value,
       name: name,
       age: age
   }
}

bar.myCall(null); // 2

console.log(bar.myCall(obj, 'kevin', 18)); //1
// Object {
//    value: 1,
//    name: 'kevin',
//    age: 18
// }

apply实现

apply和call唯一的区别是:call传入的是参数列表,apply传入的是数组,也可以是类数组

Function.prototype.myApply = function(context, arg) {
   // 如果第一个参数传入的是undefined和null,context为window对象
   context = context || window;  

// context对象添加函数bar
   context.fn = this;   // this:bar,this指向调用myCall的函数bar  

// context对象执行函数bar,并返回结果
   let result = null;
   if (!arg) { // 没有传入数组
       result = context.fn();
   }else{      // 传入了参数数组
       result = context.fn(...arg);
   }
   return result;
}

// 测试一下
var value = 2;

var obj = {
   value: 1
}
function bar(name, age) {
   console.log(this.value);
   return {
       value: this.value,
       name: name,
       age: age
   }
}

bar.myApply(null); // 2
console.log(bar.myApply(obj, ['kevin', 18])); // 1

bind实现

  • bind和call、apply的区别: bind返回的是一个改变了this指向的函数,便于稍后调用,不像call和apply会立即调用;bind和call很像,传入的也是参数列表,但是可以多次传入,不需要像call,一次传入

  • 值得注意:当 bind 返回的函数 使用new作为构造函数时,绑定的 this 值会失效,this指向实例对象,但传入的参数依然生效 (new调用的优先级 > bind调用)

bind实现最为复杂,因为经过bind绑定过的函数,既可以被当作普通函数调用,又可以被当作构造函数调用

  • bind 返回的函数 作为普通函数调用

// bind 返回的函数 作为普通函数调用
let bindFun = normalFun.myBind(obj, '我是参数传进来的name') // this:obj
bindFun('我是参数传进来的age')
  • bind 返回的函数 作为构造函数调用,绑定的 this 值obj会失效,this指向实例对象a

// bind 返回的函数 作为构造函数调用
let bindFun = Person.myBind(obj, '我是参数传进来的name') // this:obj
let a = new bindFun('我是参数传进来的age')               // this:a

bind 返回的函数 作为普通函数调用 代码实现

// bind 返回的函数 作为普通函数调用
Function.prototype.myBind = function (context, ...args){
   // 如果第一个参数传入的是undefined和null,context为window对象
   context = context || window;

// context对象(obj)添加函数normalFun
   context.fn = this;  // this:normalFun, context.fn === normalFun,下面出现context.fn都可以直接看成normalFun

// bind返回的函数  
   return function (...innerArgs) {
       // bind 返回的函数 作为普通函数被执行
       context.fn(...[...args,...innerArgs]);  //相当于normalFun(...[...args,...innerArgs])
   }
}

// 测试
let obj = {
 objName: '我是obj传进来的name',
 objAge: '我是obj传进来的age'
}
// 普通函数
function normalFun(name, age) {
 console.log(name);          //'我是第一次参数传进来的name被args接收'
 console.log(age);           //'我是第二次参数传进来的age被innerArgs接收'
 console.log(this === obj);  // true,this指向obj
 console.log(this.objName);  //'我是obj传进来的name'
 console.log(this.objAge);   //'我是obj传进来的age'
}

// bind 返回的函数 作为普通函数调用
let bindFun = normalFun.myBind(obj, '我是第一次参数传进来的name被args接收'); // this指向obj
bindFun('我是第二次参数传进来的age被innerArgs接收');

bind 返回的函数 作为构造函数调用

// bind 返回的函数 再经过new调用  
Function.prototype.myBind = function (context, ...args){
   // 如果第一个参数传入的是undefined和null,context为window对象
   context = context || window;

// context对象添加函数Person
   context.fn = this;     // this:Person,context.fn:Person,_this:Person
   let _this = this;

// bind返回的函数
   const result = function (...innerArgs) {
       if (this instanceof _this ) { // this:a (new出来的实例对象) ,  _this:Person
           // 为实例对象a添加Person方法
           this.fn = _this;
           // 实例对象a执行Person方法
           this.fn(...[...args,...innerArgs]);
       }
   }
   result.prototype = Object.create(this.prototype);  // 为什加这一句?看原型图下面会解释
   return result;
}
// 测试
function Person(name, age) {
 console.log(name); //'我是第一次参数传进来的name被args接收'
 console.log(age); //'我是第二次参数传进来的age被innerArgs接收'
 console.log(this); //构造函数this指向实例对象
}
// 构造函数原型的方法
Person.prototype.say = function() {
 console.log(123);
}

let obj = {
 objName: '我是obj传进来的name',
 objAge: '我是obj传进来的age'
}

// bind 返回的函数 作为构造函数调用
let bindFun = Person.myBind(obj, '我是第一次参数传进来的name被args接收') // this:obj
let a = new bindFun('我是第二次参数传进来的age被innerArgs接收')               // this:a
a.say() //123

画以下两条语句的原型图方便理解

let bindFun = Person.myBind(obj, '我是第一次参数传进来的name被args接收') // this:obj
let a = new bindFun('我是第二次参数传进来的age被innerArgs接收')               // this:a

当执行下面语句时,原型图如下:

let bindFun = Person.myBind(obj, '我是第一次参数传进来的name被args接收') // this:obj

JavaScript中call,apply,bind的区别与实现

当执行下面语句时,bindFun就是result看代码,原型图如下:

let a = new bindFun('我是第二次参数传进来的age被innerArgs接收')               // this:a

JavaScript中call,apply,bind的区别与实现

在这里实例对象a还需要继承构造函数Person的原型,所以加上了

result.prototype = Object.create(this.prototype);

原型图最终如下:

JavaScript中call,apply,bind的区别与实现

bind代码最终实现

Function.prototype.myBind = function (context, ...args){
   // 如果第一个参数传入的是undefined和null,context为window对象
   context = context || window;

// context对象添加函数Person
   context.fn = this;     // this:Person,context.fn:Person,_this:Person
   let _this = this;

// bind返回的函数
   const result = function (...innerArgs) {
       if (this instanceof _this ) { // this:a (new出来的实例对象) ,  _this:Person
           // 为实例对象a添加Person方法
           this.fn = _this;
           // 实例对象a执行Person方法
           this.fn(...[...args,...innerArgs]);
       }else{
           // 普通函数被调用
           context.fn(...[...args,...innerArgs]);
       }
   }

result.prototype = Object.create(this.prototype);  // 为什加这一句?看原型图下面会解释
   return result;
}

// 测试
// function Person(name, age) {
//   console.log(name); //'我是第一次参数传进来的name被args接收'
//   console.log(age); //'我是第二次参数传进来的age被innerArgs接收'
//   console.log(this); //构造函数this指向实例对象
// }
// // 构造函数原型的方法
// Person.prototype.say = function() {
//   console.log(123);
// }

// let obj = {
//   objName: '我是obj传进来的name',
//   objAge: '我是obj传进来的age'
// }

// // bind 返回的函数 作为构造函数调用
// let bindFun = Person.myBind(obj, '我是第一次参数传进来的name被args接收') // this:obj
// let a = new bindFun('我是第二次参数传进来的age被innerArgs接收')               // this:a
// a.say() //123

// 测试
let obj = {
 objName: '我是obj传进来的name',
 objAge: '我是obj传进来的age'
}

// 普通函数
function normalFun(name, age) {
 console.log(name);          //'我是第一次参数传进来的name'
 console.log(age);           //'我是第二次参数传进来的age'
 console.log(this === obj);  // true
 console.log(this.objName);  //'我是obj传进来的name'
 console.log(this.objAge);   //'我是obj传进来的age'
}

// bind 返回的函数 作为普通函数调用
let bindFun = normalFun.myBind(obj, '我是第一次参数传进来的name被args接收'); // this指向obj
bindFun('我是第二次参数传进来的age被innerArgs接收');

来源:https://juejin.cn/post/7142775964009758757

标签:JavaScript,call,apply,bind,区别
0
投稿

猜你喜欢

  • MySQL8.0.18配置多主一从

    2024-01-15 01:23:15
  • fckeditor 常用函数

    2023-01-25 15:47:11
  • 浅探express路由和中间件的实现

    2024-05-11 10:17:08
  • Python实现查找数组中任意第k大的数字算法示例

    2022-04-26 22:10:46
  • Python针对给定列表中元素进行翻转操作的方法分析

    2022-04-19 18:37:07
  • 《色彩解答》系列之三 色彩对比

    2008-02-17 14:40:00
  • js处理括弧配对替换的方法

    2008-01-16 13:48:00
  • python根据文件大小打log日志

    2022-04-28 06:29:35
  • GoLang 逃逸分析的机制详解

    2023-08-06 16:46:43
  • Pytorch环境搭建与基本语法

    2021-04-22 21:57:47
  • 人工智能学习Pytorch梯度下降优化示例详解

    2023-02-11 16:28:02
  • go语言实现mqtt协议的实践

    2024-04-23 09:34:38
  • Mysql全局ID生成方法

    2023-07-02 13:59:53
  • Python中的is和==比较两个对象的两种方法

    2021-09-15 21:23:14
  • 优化代码 改善CSS文件可读性

    2008-06-13 13:50:00
  • Python多进程编程技术实例分析

    2022-07-23 18:02:49
  • python pandas cumsum求累计次数的用法

    2021-08-07 01:51:19
  • 一文详解Python中生成器的原理与使用

    2021-11-29 16:52:55
  • 如何使用ASP实现网站的“目录树”管理

    2008-06-13 06:39:00
  • Python使用回溯法子集树模板获取最长公共子序列(LCS)的方法

    2021-04-14 04:55:49
  • asp之家 网络编程 m.aspxhome.com