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'); // 自动适配
相关推荐
天问一3 小时前
使用 Vue Router 进行路由定制和调用的示例
前端·javascript·vue.js
韩立学长5 小时前
【开题答辩实录分享】以《基于Vue的非遗文化知识分享平台的设计与实现》为例进行选题答辩实录分享
前端·javascript·vue.js
前端 贾公子5 小时前
Vue响应式原理学习:基本原理
javascript·vue.js·学习
飛6795 小时前
从 0 到 1 掌握 Flutter 状态管理:Provider 实战与原理剖析
开发语言·javascript·ecmascript
~无忧花开~6 小时前
Vue二级弹窗关闭错误解决指南
开发语言·前端·javascript·vue.js
知行力7 小时前
【GitHub每日速递 20251209】Next.js融合AI,让draw.io图表创建、修改、可视化全靠自然语言!
javascript·人工智能·github
REDcker7 小时前
JS 与 C++ 语言绑定技术详解
开发语言·javascript·c++
zlpzlpzyd7 小时前
vue.js 3中全局组件和局部组件的区别
前端·javascript·vue.js
浩星7 小时前
css实现类似element官网的磨砂屏幕效果
前端·javascript·css