JS 高手必会:手写 new 与 instanceof

手写 instanceof

首先我们需要了解 instanceof是啥?

在其他面向对象编程语言中instanceof大多为实例判断运算符,即检查对象是否是某个构造函数的实例。

但是在 JS 中,instanceof原型关系判断运算符 ,用于检测构造函数的prototype属性是否出现在某个对象的原型链上。

js 复制代码
// object instanceof Constructor
A instanceof B // A 的原型链上是否有 B 的原型 

而在大型项目、多人协作的情况下,在搞不懂对象上有哪些属性和方法,通过instanceof来查找继承关系

原型链关系:

  • __proto__: 指向原型对象(上一位的 .prototype),用于属性查找
  • constructor: 指向构造函数本身,用于标识对象的创建者
  • prototype: 函数的属性,指向原型对象(内置构造函数的 prototype 上通常有默认方法)
js 复制代码
子.__proto__ === 父.prototype
父.prototype.constructor === 父
子.__proto__.constructor === 父(通过原型链访问)

举个最简单的例子:

js 复制代码
const arr = [];
console.log(
    arr.__proto__, // Array.prototype
    arr.__proto__.__proto__, // Object.prototype
    arr.__proto__.__proto__.__proto__ // null
)

那么arr的原型链关系就是:arr -> Array.prototype -> Object.prototype -> null

而我们要手写instanceof,也就是只需要沿着原型链去查找,那么用简单的循环即可。

手写代码如下

js 复制代码
// right 是否出现在 left 的原型链上
function isInstanceOf(left, right) {
    let proto = left.__proto__;
    // 循环查找原型链
    while (proto) {
        if (proto === right.prototype) {
            return true;
        }
        proto = proto.__proto__; // null 结束循环
    }
    return false;
}

手写 new

new对我们来说并不陌生,也就是实例运算符

在之前的文章我也提到过new的伪代码,让我们再来复习一下:

js 复制代码
// 从空对象开始
let obj = {}
// this -> 创建空对象,运行构造函数
Object.call(obj)
// 将空对象的__proto__ 指向 构造函数的原型对象
obj.__proto__ = Object.prototype
// 返回新对象
return obj

写法一:(es6 新写法)

假如我们要new一个实例对象,但是不知道构造函数上的参数数量,而在es6中有一个新的运算符,也就是...运算符,它的诸多功能就可以满足我们的需求。

...扩展运算符

...有双重身份,在不同情况下的作用也不同

  • 函数调用 / 数组 / 对象字面量中...称为扩展运算符,将可迭代对象"展开"为独立元素

  • 函数参数 / 解构赋值中 : ...称为剩余参数/剩余元素,将多个值"收集"为一个数组

js 复制代码
// 展开数组
const arr1 = [1, 2, 3]; 
const arr2 = [...arr1, 4, 5]; // [1, 2, 3, 4, 5] 

// 收集数组
const [first, ...rest] = [1, 2, 3, 4];
console.log(first); // 1
console.log(rest);  // [2, 3, 4]

而依据我们的伪代码即可模拟 new的功能

手写代码如下

js 复制代码
function objectFactory(Construstor, ...args) {
    // 创建空对象
    var obj = new Object(); 
    // 绑定 this 并执行构造函数
    Construstor.apply(obj, args); // 不能使用call,因为apply调用数组
    // 设置原型链
    obj.__proto__ = Construstor.prototype; 
    return obj;
}

// 使用:完全不需要知道 Person 需要几个参数
function Person(name, age, city) {
  this.name = name;
  this.age = age;
  this.city = city;
}

const p = objectFactory(Person, 'Alice', 25, 'Beijing'); // 自动适配

写法二:(根据arguments es5)

当然,在es6之前我们并没有...运算符,那么如何手写new呢?这就不得不提到arguments了。

arguments 是什么?

arguments 是 JS 中的一个类数组对象 ,它在所有非箭头函数内部自动可用,用于访问传递给该函数的所有实参

类数组对象:

  • 拥有 length 属性和若干索引属性,但不具备数组原型方法(如 .push(), .map(), .forEach() 等)的对象,所以其不是真正的数组

  • 普通函数中自动绑定。

  • 箭头函数内部没有自己的 arguments ,但是会沿作用域链查找外层函数的 arguments,如果外层有就用外层的。

不妨来看个例子理解一下:

js 复制代码
function greet() {
  console.log(arguments); // 类数组对象
  console.log(arguments.length); // 实际传入参数个数
  console.log(arguments[0]);     // 第一个参数
}
greet('Alice', 'Bob'); // 输出: { '0': 'Alice', '1': 'Bob' }, length: 2, 'Alice'

如何将 arguments 转为真数组?

因为 arguments 不是数组,不能直接用数组方法。但是可以将其转换为数组:

方法 1:扩展运算符(ES6+,最简洁)

js 复制代码
function fn() {
  const args = [...arguments];
  args.map(x => x * 2); // 可用数组方法
}

方法 2:Array.from()

js 复制代码
const args = Array.from(arguments);

方法 3:[].slice.call()

js 复制代码
const args = Array.prototype.slice.call(arguments);
// 或简写(更推荐)
const args = [].slice.call(arguments);

slice是数组原型上的一个方法,用于返回一个从原数组或类数组中浅拷贝出来的新数组(实现将类数组转换成数组)

[].slice是因为arguments上没有这个方法,所以需要去空数组中"借用",并且通过 .call()slicethis指向arguments,变相的让arguments可以使用这个方法。

在了解了arguments后,聪明的你已经想到了如何通过它来实现手写new

手写代码如下

js 复制代码
function objectFactory() {
    // 创建空对象
    var obj = new Object(); 
    // 将 arugments 的第一项提出来(也就是 构造函数)
    var Construstor = [].shift.call(arguments);
    // 绑定 this 并执行构造函数
    Construstor.apply(obj, arguments); // 不能使用call,因为 apply调用数组
    // 设置原型链
    obj.__proto__ = Construstor.prototype; 
    return obj;
}

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

const p = objectFactory(Person, 'Alice', 25, 'Beijing'); // 自动适配
相关推荐
低保和光头哪个先来1 小时前
场景6:对浏览器内核的理解
开发语言·前端·javascript·vue.js·前端框架
ji_shuke2 小时前
canvas绘制拖拽箭头
开发语言·javascript·ecmascript
2501_946244782 小时前
Flutter & OpenHarmony OA系统设置页面组件开发指南
开发语言·javascript·flutter
cz追天之路3 小时前
华为机考 ------ 识别有效的IP地址和掩码并进行分类统计
javascript·华为·typescript·node.js·ecmascript·less·css3
l1t3 小时前
DeepSeek总结的算法 X 与舞蹈链文章
前端·javascript·算法
千寻girling4 小时前
面试官 : “ 说一下 localhost 和127.0.0.1 的区别 ? ”
前端·javascript·面试
YAY_tyy4 小时前
Turfjs+Three.js:地理数据的三维建模应用
前端·javascript·3d·arcgis·turfjs
@淡 定4 小时前
DDD领域事件详解:抽奖系统实战
开发语言·javascript·网络
汐泽学园4 小时前
基于Vue的幼儿绘本阅读启蒙网站设计与实现
前端·javascript·vue.js
mikan5 小时前
详解把老旧的vue2+vue-cli+node-sass项目升级为vite
前端·javascript