JS手写bind之处理new的情况详解

目录
  • new
  • 判断函数是否通过 new 被调用
  • new 和 bind
  • 实现完整的 bind
  • 结尾

大家好,我是前端西瓜哥。

之前写了一篇关于 JS 中 bind 方法的实现的文章,并给出了实现:

Function.prototype.myBind = function(thisArg, ...prefixArgs) {
  const fn = this;
  return function(...args) {
    return fn.call(thisArg, ...prefixArgs, ...args);
  }
}

但没有处理 通过 new 创建实例 的情况。

因为很少会遇到给 bind 返回的函数做 new 操作的场景,所以我没去考虑这种特殊情况。

但面试中还是会涉及到的,我们还是实现一下兼容 new 操作的 bind 写法,顺便学习一下 new 操作符。

因为存在一定上下文,在阅读本文前,建议先阅读前一篇文章:《前端面试题:手写 bind》。

new

我们先学习一下 new 操作符。

new 用于通过函数来创建一个对象实例,在很多语言中都能看到。

JS 的函数,除了可以是普通函数,比如:

function sum(a, b) {
  return a + b;
}

还可以是构造函数,只需要在构造时在它前面加一个 new:

function Person(name, age) {
 this.name = name;
 this.age = age;
}

const person = new Person('前端西瓜哥', 100)
// Person {name: '前端西瓜哥', age: 100}

new 创建一个新对象,做了下面几件事:

  • 创建一个空对象 {}
  • 空对象的原型属性 __proto__ 指向构造函数的原型对象 Person.prototype
  • 函数中的 this 设置为这个空对象;
  • 如果该函数不返回一个对象,就返回这个 this,否则返回这个对象。

判断函数是否通过 new 被调用

怎么判断一个函数正在被 new 操作符调用?

答案是 使用 instanceof 判断 this 是否为当前函数的实例,即 this instanceof Fn 为 true,表示在通过 new 构建实例。

我们看一个例子:

function Person() {
  if (this instanceof Person) {
    console.log('通过 new 构建实例');
  } else {
    console.log('普通调用')
  }
}

Person()     // 输出:普通调用
new Person() // 输出:通过 new 构建实例

在 Vuejs 的源码,你会看到下面代码,这里也用到了这个技巧。

function Vue(options) {
  if (__DEV__ && !(this instanceof Vue)) {
    warn('Vue is a constructor and should be called with the `new` keyword')
  }
  this._init(options)
}

你在开发环境如果不通过 new 来使用 Vue 对象,会在控制台提示你要通过 new 来调用 Vue。

new 和 bind

如果我们 new 的是 Function.prototype.bind 返回的新函数,会发生什么事情?

function Person(name, age) {
 this.name = name;
 this.age = age;
}

const BoundPerson = Person.bind(null, '前端西瓜哥');
const boundPerson = new BoundPerson(100);
// Person {name: '前端西瓜哥', age: 100}

boundPerson.__proto__ === Person.prototype
// true

结果等价于直接去 new 原始函数。

不同的是,仍旧可以进行参数的预置。可以看到,构造函数的第一个参数,在调用 bind 的时候就提前设置为 '前端西瓜哥'。

实现完整的 bind

完整实现如下:

Function.prototype.myBind = function(thisArg, ...prefixArgs) {
  const fn = this;
  const boundFn = function(...args) {
    // 通过 new 使用当前函数
    if (this instanceof boundFn) {
      return new fn(...prefixArgs, ...args);
    }
    // 普通的方法调用当前函数
    return fn.call(thisArg, ...prefixArgs, ...args);
  }
  boundFn.prototype = fn.prototype;
  return boundFn;
}

这里我通过 this instanceof boundFn 来判断是否用了 new,如果是,就直接 new 原始函数然后返回,记得带上 bind 预置好的参数。

其他保持原样(具体见上文《前端面试题:手写 bind》)。

boundFn.prototype = fn.prototype;  这个可写可不写,只是让 bind 返回的新函数的 prototype 指向原函数的 prototype。

如果是原生 bind 返回的函数,它是没有 protoype 属性的,可以认为它是一种特别的函数,而我们实现的 bind 返回的却是一个普通函数,所以并不能完全模拟的。

如果你 追求完美的实现,可以研读一下Function.prototype.bind的标准:

然后再看看知名的 core.js 库中对 bind 的实现:

其中核心实现为:

// `Function.prototype.bind` method implementation
// https://tc39.es/ecma262/#sec-function.prototype.bind
module.exports = Function.bind || function bind(that /* , ...args */) {
  var F = aCallable(this);
  var Prototype = F.prototype;
  var partArgs = arraySlice(arguments, 1);
  var boundFunction = function bound(/* args... */) {
    var args = concat(partArgs, arraySlice(arguments));
    return this instanceof boundFunction ? construct(F, args.length, args) : F.apply(that, args);
  };
  if (isObject(Prototype)) boundFunction.prototype = Prototype;
  return boundFunction;
};

这里有更多的细节:

  • 这里判断了 this 是否为函数类型,不是函数会报错;
  • F.prototype 需要是一个对象或函数,才能赋值给新函数;
  • 使用了普通函数和 arguments,这是为了兼容 ES5。

结尾

手写 bind,现在想来并不简单,需要掌握好很多知识点:

  • bind 的详尽用法:包括改变 this、预置参数、new 的表现;
  • 闭包的使用:保存一些私有变量;
  • 通过原型链的方式(this instanceof boundFn)判断是否通过 new 调用当前函数;
  • 使用 call 在执行时改变函数的 this 指向。

到此这篇关于JS手写bind之处理new的情况详解的文章就介绍到这了,更多相关JS处理new内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 手写实现JS中的new

    目录 1 new 运算符简介 2 new 究竟干了什么事 3 模拟实现 new 运算符 4 补充 ⚠ 预备知识: 了解原型和原型链 了解this绑定 1 new 运算符简介 MDN文档:new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例. class Person { constructor(name) { this.name = name; } } // 创建自定义对象类型的实例 const person = new Person('小明') // 创建具有构造函数的

  • JS new操作原理及手写函数模拟实现示例

    目录 引言 原理 手写函数模拟 new v1 基本实现 v2 考虑参数类型 v3 Object.prototype.__proto__ 的替代方案 v4 使用 ES6 语法实现 v5 考虑 ES6 的 new.target 检测 无注释版本 引言 我们知道,在 ES6 之前(ES5),JavaScript 中类的表现形式就是构造函数. JavaScript 中的构造函数是怎么样的? 构造函数也是一个普通的函数,从表现形式上看,和普通的函数没有任何区别(除了按照惯例,构造函数名称的首字母通常会大写

  • 深入聊一聊JS中new的原理与实现

    定义 new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例. 使用new [constructor]的方式来创建一个对象实例,但构造函数的差异会导致创建的实例不同. 构造函数体不同 构造函数也是函数,其唯一的区别就是调用方式不同,任何函数只要使用 new 操作符调用就是构造函数,而不使用 new 操作符调用的函数就是普通函数. 因此构造函数也可以带有返回值,但是这会导致new的结果不同. 无返回值 function Person(name) { this.name = n

  • JS 中 new 的作用详细

    目录 1.举例 2.制造一百个士兵 3.质疑 4.改进 5.优雅? 6.JS 之父的关怀 7.这一次我们用 new 来写 8.注意 constructor 属性 简介: 大部分讲 new 的文章会从面向对象的思路讲起,但是我始终认为,在解释一个事物的时候,不应该引入另一个更复杂的事物. 想象我们在制作一个策略类战争游戏,玩家可以操作一堆士兵攻击敌方. 我们着重来研究一下这个游戏里面的「制造士兵」环节. 一个士兵的在计算机里就是一堆属性,如下图: 1.举例 我们只需要这样就可以制造一个士兵: va

  • JS前端面试题详解之手写bind

    目录 bind 的用法 this 的指向问题 积累参数 实现一个 bind 结尾 大家好,我是前端西瓜哥,今天我们用 JS 来实现内置的 bind 方法. bind 的用法 在实现之前,我们先学习一下 Function.prototype.bind 的用法. function.bind(thisArg[, arg1[, arg2[, ...]]]) bind 是函数特有的一个方法,可以创建一个绑定了 this 的新函数. 接受的参数为如下. 第 1 个参数 thisArg:用于修改 this 指

  • JS手写bind之处理new的情况详解

    目录 new 判断函数是否通过 new 被调用 new 和 bind 实现完整的 bind 结尾 大家好,我是前端西瓜哥. 之前写了一篇关于 JS 中 bind 方法的实现的文章,并给出了实现: Function.prototype.myBind = function(thisArg, ...prefixArgs) { const fn = this; return function(...args) { return fn.call(thisArg, ...prefixArgs, ...arg

  • Java 通过手写分布式雪花SnowFlake生成ID方法详解

    目录 SnowFlake算法 SnowFlake优点: SnowFlake算法 SnowFlake算法生成id的结果是一个64bit大小的整数,它的结构如下图: 分为四段: 第一段: 1位为未使用,永远固定为0. (因为二进制中最高位是符号位,1表示负数,0表示正数.生成的id一般都是用正整数,所以最高位固定为0 ) 第二段: 41位为毫秒级时间(41位的长度可以使用69年) 第三段: 10位为workerId(10位的长度最多支持部署1024个节点) (这里的10位又分为两部分,第一部分5位表

  • Java 通过手写分布式雪花SnowFlake生成ID方法详解

    目录 SnowFlake算法 SnowFlake优点 SnowFlake不足 SnowFlake算法 SnowFlake算法生成id的结果是一个64bit大小的整数,它的结构如下图: 分为四段: 第一段: 1位为未使用,永远固定为0. (因为二进制中最高位是符号位,1表示负数,0表示正数.生成的id一般都是用正整数,所以最高位固定为0 ) 第二段: 41位为毫秒级时间(41位的长度可以使用69年) 第三段: 10位为workerId(10位的长度最多支持部署1024个节点) (这里的10位又分为

  • (手写)PCA原理及其Python实现图文详解

    目录 1.背景 2.样本均值和样本方差矩阵 3.PCA 3.1 最大投影方差 3.2 最小重构距离 4.Python实现 总结 1.背景 为什么需要降维呢? 因为数据个数 N 和每个数据的维度 p 不满足 N >> p,造成了模型结果的"过拟合".有两种方法解决上述问题: 增加N;减小p. 这里我们讲解的 PCA 属于方法2. 2.样本均值和样本方差矩阵 3.PCA 3.1 最大投影方差 3.2 最小重构距离 4.Python实现 """ -*

  • 浅谈时钟的生成(js手写简洁代码)

    在生成时钟的过程中自己想到布置表盘的写法由这么几种: 当然利用那种模式都可以实现,所以我们要用一个最好理解,代码有相对简便的方法实现 1.利用三角函数 用js在三角函数布置表盘的过程中有遇见到这种情况:是在表盘的刻度处,利用三角函数计算具体的值时不能得到整数,需要向上或者向下取整,这样无形中就会存在些许偏差,而且这样的偏差难利用样式来调整到位,即使最终效果都可以实现,但是细微处的缝隙和角度的偏差都会影响整体的视觉体验,作为一名程序开发人员,这样的视觉体验很难让别人认可,放弃. 2.利用遮罩层 j

  • JS手写一个自定义Promise操作示例

    本文实例讲述了JS手写一个自定义Promise操作.分享给大家供大家参考,具体如下: 经常在面试题中会看到,让你实现一个Promsie,或者问你实现Promise的原理,所以今天就尝试利用class类的形式来实现一个Promise 为了不与原生的Promise命名冲突,这里就简单命名为MyPromise. class MyPromise { constructor(executor) { let _this = this this.state = 'pending' // 当前状态 this.v

  • Node.js高级编程cluster环境及源码调试详解

    目录 前言 准备调试环境 编译 Node.js 准备 IDE 环境 Cluster 源码调试 SharedHandle RoundRobinHandle 为什么端口不冲突 SO_REUSEADDR 补充 SharedHandle 和 RoundRobinHandle 两种模式的对比 前言 日常工作中,对 Node.js 的使用都比较粗浅,趁未羊之际,来学点稍微高级的,那就先从 cluster 开始吧. 尼古拉斯张三说过,“带着问题去学习是一个比较好的方法”,所以我们也来试一试. 当初使用 clu

  • js单页hash路由原理与应用实战详解

    本文主要介绍了js单页hash路由原理与应用实战详解,分享给大家,具体如下: 什么是路由? 通俗点说,就是不同的URL显示不同的内容 什么是单页应用? 单页,英文缩写为SPA( Single Page Application),就是把各种功能做在一个页面内. 那所谓的单页路由应用就是:在一个页面内,通过切换地址栏的URL来实现切换内容的变化. 如何知道URL切换了呢? 当url后面的锚文本发生变化时, 会触发onhashchange事件.通过这个事件,我们就可以对不同的URL 做出不同的处理.锚

  • JS图片轮播与索引变色功能实例详解

    废话不多说了,直接给大家贴代码了,具体代码如下所示: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <met

随机推荐