您当前的位置:首页 > 电脑百科 > 程序开发 > 语言 > javascript

Javascript的New、Apply、Bind、Call知多少

时间:2021-12-06 13:34:28  来源:  作者:Nodejs开发

作者:一川来源:前端万有引力

Javascript的New、Apply、Bind、Call知多少

 

1 写在前面

JAVAscript中的Apply、call、bind方法是前端代码开发中相当重要的概念,并且与this的指向密切相关。本篇文章我们将深入探讨这个关键词的作用,并尝试进行手动编写复现。

阅读文章前,我们带着几个问题进行研究:

  • 用什么样的思路可以new关键字?
  • apply、bind、call这三个方法有什么区别?
  • 怎样手动实现一个apply、bind和call?

2 new关键词

new关键词的作用是执行一个构造函数,返回一个实例对象,根据构造函数的情况来确定是否可以接受参数的传递。

2.1 new的原理

使用new进行实例化对象,其步骤是:

  1. 创建一个新的空对象,即{}
  2. 将该对象构造函数的作用域赋给新对象,this指向新对象(即将新对象作为this的上下文)
  3. 执行构造函数中的代码,为这个新对象添加属性
  4. 如果该对象构造函数没有返回对象,则返回this
function Person(){ 
 this.name = "yichuan" 
} 
 
const p = new Person(); 
console.log(p.name);//"yichuan" 

我们可以看到当使用new进行实例化时,可以将构造函数的this指向新对象p。当不使用new时,此时构造函数的this指向window。

function Person(){ 
 this.name = "yichuan" 
} 
 
const p = Person(); 
console.log(p);//undefined 
console.log(name);//"yichuan"   window.name 
console.log(p.name);//"yichuan" is undefined 

当我们在构造函数中直接返回一个和this无关的对象时,使用new关键字进行实例化对象,新生成的对象就是构造函数返回的对象,而非构造函数的this的对象。

function Person(){ 
 this.name = "yichuan"; 
  return {age:18}; 
} 
 
const p = new Person(); 
console.log(p);//{age:18} 
console.log(p.name);//"undefined" 
console.log(p.age);/18 

此外,当构造函数返回的不是一个对象,而是基础数据类型的值时,使用new创建新对象,会将构造函数返回的值以对象形式给新对象。

function Person(){ 
 this.name = "yichuan"; 
  return "onechuan"; 
} 
 
const p = new Person(); 
console.log(p);//{name:"yichuan"} 
console.log(p.name);//"yichuan" 

new关键词执行之后总是会返回一个对象,要么是实例,要么是return语句指定的对象。

2.2 手写new的实现

new被调用后大致做了哪些事情?

  1. 让实例可以访问私有属性
  2. 让实例可以访问构造函数原型(constructor.prototype)所在原型链上的属性
  3. 构造函数返回的最后结果是引用数据类型
function new_object(ctor,...args){ 
 //先要判断ctor是否为一个函数 
  if(typeof ctor !== "function"){ 
   throw "ctor must be a function"; 
  } 
  //创建一个空对象 
  const obj = new Object(); 
  //将实例obj可以访问到ctor原型所在原型链的属性 
  obj.__proto__ = Object.create(ctor.prototype); 
  //将构造函数的this指向实例对象obj 
  const res = ctor.apply(obj,...args); 
  //确保最后new返回的是一个对象 
  const isObject = typeof res === "object" && typeof res!== null; 
  const isFunction = typeof res === "function"; 
  return isObject || isFunction ? res : obj; 
} 

当然,我们还可以进行优化以下:

function new_object() { 
  // 1、获得构造函数,同时删除 arguments 中第一个参数 
  const ctor = [].shift.call(arguments);//其实这里是借用了数组的shift方法 
  // 2、创建一个空的对象并链接到原型,obj 可以访问构造函数原型中的属性 
  const obj = Object.create(ctor.prototype); 
  // 3、绑定 this 实现继承,obj 可以访问到构造函数中的属性 
  const ret =ctor.apply(obj, arguments); 
  // 4、优先返回构造函数返回的对象 
  return ret instanceof Object ? ret : obj; 
}; 

3 apply、bind以及call

apply、bind和call是挂载Function对象上的三个方法,调用这三个方法的必须是一个函数。

3.1 apply

apply() 方法调用一个具有给定 this 值的函数,以及作为一个数组(或类似数组对象)提供的参数。apply()方法可以改变函数this的指向,且立即执行函数。

注意:Chrome 14 以及 Internet Explorer 9 仍然不接受类数组对象。如果传入类数组对象,它们会抛出异常。

func.apply(thisArg, [param1,param2,...]); 

在使用apply时,会将func的this指向改变为指向thisArg,然后以[param1,param2,...]参数数组作为参数输入。

func(["red","green","blue"]); 
func.apply(newFun, ["red","green","blue"]); 

我们可以看到都执行func时,第一个func函数的this指向的是window全局对象,而第二个func函数的this指向的是newFun。

Function.prototype.apply = function (context, arr) { 
    context = context ? Object(context) : window;  
    context.fn = this; 
   
    let result; 
   //判断有没有参数数组输入 
    if (!arr) { 
        result = context.fn(); 
    } else { 
        result = context.fn(...arr); 
    } 
   //此处也可以使用eval进行处理 
   // const result = eval("context.fn(...arr)"); 
       
    delete context.fn 
    return result; 
} 

3.2 bind

bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。

bind(thisArg,param1,param2,...); 

其实,bind的实现思路基本和apply一致,但是在最后实现返回结果时,bind不需要直接执行,而是以返回函数的形式返回结果,之后再通过执行这个结果即可。

先分析下bind的特性:首先是指定新对象this指向,再传入参数返回一个定义的函数,最后使用柯里化进行调用。同样的,我们也可以根据这些特性进行手动封装一个bind函数:

Function.prototype.bind = function(context){ 
 //先要判断调用bind函数的是不是函数,需要抛出异常 
  if(typeof this !== "function"){ 
   throw new Error("this bind function must be userd to function"); 
  } 
  //存储this的指向 
  const self = this; 
  //context是新对象this指向的目标对象,而参数就是在第一个参数之后的参数 
  const args = Array.prototype.slice.call(arguments,1); 
   
  //创建一个空对象 
  const fun = function(){} 
   
  //返回一个函数 
  const funBind = function(){ 
   //返回所有的参数给bind函数 
    const bindArg = Array.prototype.slice.call(arguments); 
    //将传入的参数合并成一个新的参数数组,作为self.apply()的第二个参数 
    return self.apply(this instanceof fun ? this : context,  args.concat(bindArgs)); 
    /**********************说明************************************/ 
  } 
   
  //空对象的原型指向绑定函数的原型 
  fun.prototype = this.prototype; 
  //空对象的实例赋值给 funBind.prototype 
  funBind.prototype = new fun(); 
  return funBinf; 
} 

补充说明:

  • this instanceof fun返回为true时,表示的是fun是一个构造函数,其this指向实例,直接将context作为参数输入
  • this instanceof fun返回为false时,表示的是fun是一个普通函数,其this指向顶级对象window,将绑定函数的this指向context对象

当然,我们也可以写成这种形式:

Function.prototype.bind = function(context,...args){ 
 //先要判断调用bind函数的是不是函数,需要抛出异常 
  if(typeof this !== "function"){ 
   throw new Error("this bind function must be userd to function"); 
  } 
  //存储this的指向 
  const self = this; 
   
  const fBind = function(){ 
   self.apply(this instanceof self ? this: context, args.concat(Array.prototype.slice.call(arguments)));  
  } 
  if(this.prototype){ 
   fBind.prototype = Object.create(this.prototype); 
  } 
  return fBind; 
} 

注意:Object.create()是es2015语法引入的新特性,因此在IE<9的浏览器是不支持的。

3.3 call

call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。使用调用者提供的 this 值和参数调用该函数的返回值。若该方法没有返回值,则返回 undefined。

function.call(thisArg, param1, param2, ...) 

注意:该方法的语法和作用与 apply() 方法类似,只有一个区别,就是 call() 方法接受的是一个参数列表,而 apply() 方法接受的是一个包含多个参数的数组。

call函数的实现:

Function.prototype.call = function(context,...args){ 
  //将函数设置为对象的属性 
 context = context || window; 
  context.fn = this; 
   
  //执行函数 
  const result = eval("context.fn(...args)"); 
  //删除对象的这个属性 
  delete context.fn; 
  return result; 
} 

4 参考文章

  • 《解析 bind 原理,并手写 bind 实现》
  • 《解析 call/apply 原理,并手写 call/apply 实现》
  • JavaScript核心原理精讲》

5 写在最后

在这篇文章中,我们知道apply、bind、call的区别在于:

  • apply、call改变了this指向后,会立即进行调用函数,返回的是执行结果
  • bind在改变this指向后,返回的是一个函数,需要另外再进行调用一次
  • bind、call传递的第一个参数都是this将要指向的对象,后面都是一个一个参数的形式输入
  • apply传递的第一个参数也是this将要指向的对象,后面传递的第二个参数是一个参数数组


Tags:Javascript   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
1、通过条件判断给变量赋值布尔值的正确姿势// badif (a === &#39;a&#39;) { b = true} else { b = false}// goodb = a === &#39;a&#39;2、在if中判断数组长度不为零...【详细内容】
2021-12-24  Tags: Javascript  点击:(5)  评论:(0)  加入收藏
给新手朋友分享我收藏的前端必备javascript已经写好的封装好的方法函数,直接可用。方法函数总计:41个;以下给大家介绍有35个,需要整体文档的朋友私信我,1、输入一个值,将其返回数...【详细内容】
2021-12-15  Tags: Javascript  点击:(19)  评论:(0)  加入收藏
作者:一川来源:前端万有引力 1 写在前面Javascript中的apply、call、bind方法是前端代码开发中相当重要的概念,并且与this的指向密切相关。本篇文章我们将深入探讨这个关键词的...【详细内容】
2021-12-06  Tags: Javascript  点击:(18)  评论:(0)  加入收藏
概述DOM全称Document Object Model,即文档对象模型。是HTML和XML文档的编程接口,DOM将文档(HTML或XML)描绘成一个多节点构成的结构。使用JavaScript可以改变文档的结构、样式和...【详细内容】
2021-11-16  Tags: Javascript  点击:(34)  评论:(0)  加入收藏
一、判断是否IE浏览器(支持判断IE11与edge)function IEVersion() {var userAgent = navigator.userAgent; //取得浏览器的userAgent字符串var isIE = userAgent.indexOf("comp...【详细内容】
2021-11-02  Tags: Javascript  点击:(39)  评论:(0)  加入收藏
Null、Undefined、空检查普通写法: if (username1 !== null || username1 !== undefined || username1 !== &#39;&#39;) { let username = username1; }优化后...【详细内容】
2021-10-28  Tags: Javascript  点击:(50)  评论:(0)  加入收藏
1、前言async函数,也就是我们常说的async/await,是在ES2017(ES8)引入的新特性,主要目的是为了简化使用基于Promise的API时所需的语法。async和await关键字让我们可以用一种更简...【详细内容】
2021-09-17  Tags: Javascript  点击:(61)  评论:(0)  加入收藏
为什么要使用 debugger这篇文章将介绍如何使用断点来进行 JavaScript 调试。在读这篇文章之前,需要问一个问题:为什么要使用断点来进行调试?我们首先需要认可使用断点的是必要...【详细内容】
2021-08-26  Tags: Javascript  点击:(65)  评论:(0)  加入收藏
JavaScript 可以做很多好玩的事, 从复杂的框架到处理API,有太多的东西需要学习。但是,它也能让我们只用一行就能做一些了不起的事情。1. 获得一个随机的布尔值(true/false)该函数...【详细内容】
2021-08-19  Tags: Javascript  点击:(76)  评论:(0)  加入收藏
JavaScript 提供了大量不同的处理数组的方法,这里花几分钟时间介绍 8 个项目中可以用到的数组方法。1. Array.map()使用.map() 方法,可以创建一个基于原始数组的修订版数组。....【详细内容】
2021-08-19  Tags: Javascript  点击:(95)  评论:(0)  加入收藏
▌简易百科推荐
1、通过条件判断给变量赋值布尔值的正确姿势// badif (a === &#39;a&#39;) { b = true} else { b = false}// goodb = a === &#39;a&#39;2、在if中判断数组长度不为零...【详细内容】
2021-12-24  Mason程    Tags:JavaScript   点击:(5)  评论:(0)  加入收藏
给新手朋友分享我收藏的前端必备javascript已经写好的封装好的方法函数,直接可用。方法函数总计:41个;以下给大家介绍有35个,需要整体文档的朋友私信我,1、输入一个值,将其返回数...【详细内容】
2021-12-15  未来讲IT    Tags:JavaScript   点击:(19)  评论:(0)  加入收藏
1. 检测一个对象是不是纯对象,检测数据类型// 检测数据类型的方法封装(function () { var getProto = Object.getPrototypeOf; // 获取实列的原型对象。 var class2type =...【详细内容】
2021-12-08  前端明明    Tags:js   点击:(23)  评论:(0)  加入收藏
作者:一川来源:前端万有引力 1 写在前面Javascript中的apply、call、bind方法是前端代码开发中相当重要的概念,并且与this的指向密切相关。本篇文章我们将深入探讨这个关键词的...【详细内容】
2021-12-06  Nodejs开发    Tags:Javascript   点击:(18)  评论:(0)  加入收藏
概述DOM全称Document Object Model,即文档对象模型。是HTML和XML文档的编程接口,DOM将文档(HTML或XML)描绘成一个多节点构成的结构。使用JavaScript可以改变文档的结构、样式和...【详细内容】
2021-11-16  海人为记    Tags:DOM模型   点击:(34)  评论:(0)  加入收藏
入口函数 /*js加载完成事件*/ window.onload=function(){ console.log("页面和资源完全加载完毕"); } /*jQuery的ready函数*/ $(document).ready(function(){ co...【详细内容】
2021-11-12  codercyh的开发日记    Tags:jQuery   点击:(35)  评论:(0)  加入收藏
一、判断是否IE浏览器(支持判断IE11与edge)function IEVersion() {var userAgent = navigator.userAgent; //取得浏览器的userAgent字符串var isIE = userAgent.indexOf("comp...【详细内容】
2021-11-02  V面包V    Tags:Javascript   点击:(39)  评论:(0)  加入收藏
Null、Undefined、空检查普通写法: if (username1 !== null || username1 !== undefined || username1 !== &#39;&#39;) { let username = username1; }优化后...【详细内容】
2021-10-28  前端掘金    Tags:JavaScript   点击:(50)  评论:(0)  加入收藏
今天我们将尝试下花 1 分钟的时间简单地了解下什么是 JS 代理对象(proxies)?我们可以这样理解,JS 代理就相当于在对象的外层加了一层拦截,在拦截方法里我们可以自定义一些个性化...【详细内容】
2021-10-18  前端达人    Tags:JS   点击:(51)  评论:(0)  加入收藏
带有多个条件的 if 语句把多个值放在一个数组中,然后调用数组的 includes 方法。// bad if (x === "abc" || x === "def" || x === "ghi" || x === "jkl") { //logic } // be...【详细内容】
2021-09-27  羲和时代    Tags:JS   点击:(58)  评论:(0)  加入收藏
最新更新
栏目热门
栏目头条