谈谈 JS 中new的原理与实现

  • new 做了那些事?
  • new 返回不同的类型时会有什么表现?
  • 手写 new 的实现过程

new 关键词的主要作用就是执行一个构造函数、返回一个实例对象,在 new 的过程中,根据构造函数的情况,来确定是否可以接受参数的传递。下面我们通过一段代码来看一个简单的 new 的例子

function Person(){
   this.name = 'Jack';
}
var p = new Person(); 
console.log(p.name)  // Jack

这段代码比较容易理解,从输出结果可以看出,p 是一个通过 Person 这个构造函数生成的一个实例对象。

new  操作符可以帮助我们构建出一个实例,并且绑定上 this,内部执行步骤可大概分为以下几步:

  1. 创建一个新对象
  2. 对象连接到构造函数原型上,并绑定 this(this 指向新对象)
  3. 执行构造函数代码(为这个新对象添加属性)
  4. 返回新对象

在第四步返回新对象这边有一个情况会例外:

那么问题来了,如果不用 new 这个关键词,结合上面的代码改造一下,去掉 new,会发生什么样的变化呢?我们再来看下面这段代码

function Person(){
  this.name = 'Jack';
}
var p = Person();
console.log(p) // undefined
console.log(name) // Jack
console.log(p.name) // 'name' of undefined

  • 从上面的代码中可以看到,我们没有使用 new 这个关键词,返回的结果就是 undefined。其中由于 JavaScript 代码在默认情况下 this 的指向是 window,那么 name 的输出结果就为 Jack,这是一种不存在 new 关键词的情况。
  • 那么当构造函数中有 return 一个对象的操作,结果又会是什么样子呢?我们再来看一段在上面的基础上改造过的代码。
function Person(){
   this.name = 'Jack'; 
   return {age: 18}
}
var p = new Person(); 
console.log(p)  // {age: 18}
console.log(p.name) // undefined
console.log(p.age) // 18

通过这段代码又可以看出,当构造函数最后 return 出来的是一个和 this 无关的对象时,new 命令会直接返回这个新对象而不是通过 new 执行步骤生成的 this 对象

但是这里要求构造函数必须是返回一个对象,如果返回的不是对象,那么还是会按照 new 的实现步骤,返回新生成的对象。接下来还是在上面这段代码的基础之上稍微改动一下

function Person(){
   this.name = 'Jack'; 
   return 'tom';
}
var p = new Person(); 
console.log(p)  // {name: 'Jack'}
console.log(p.name) // Jack

可以看出,当构造函数中 return 的不是一个对象时,那么它还是会根据 new 关键词的执行逻辑,生成一个新的对象(绑定了最新 this),最后返回出来

因此我们总结一下:new 关键词执行之后总是会返回一个对象,要么是实例对象,要么是 return 语句指定的对象

手工实现New的过程

function create(fn, ...args) {
  if(typeof fn !== 'function') {
    throw 'fn must be a function';
  }
	// 1、用new Object() 的方式新建了一个对象obj
  // var obj = new Object()
	// 2、给该对象的__proto__赋值为fn.prototype,即设置原型链
  // obj.__proto__ = fn.prototype

  // 1、2步骤合并
  // 创建一个空对象,且这个空对象继承构造函数的 prototype 属性
  // 即实现 obj.__proto__ === constructor.prototype
  var obj = Object.create(fn.prototype);

	// 3、执行fn,并将obj作为内部this。使用 apply,改变构造函数 this 的指向到新建的对象,这样 obj 就可以访问到构造函数中的属性
  var res = fn.apply(obj, args);
	// 4、如果fn有返回值,则将其作为new操作返回内容,否则返回obj
	return res instanceof Object ? res : obj;
};

  • 使用 Object.create 将 obj 的proto指向为构造函数的原型
  • 使用 apply 方法,将构造函数内的 this 指向为 obj
  • 在 create 返回时,使用三目运算符决定返回结果。

我们知道,构造函数如果有显式返回值,且返回值为对象类型,那么构造函数返回结果不再是目标实例

如下代码:

function Person(name) {
  this.name = name
  return {1: 1}
}
const person = new Person(Person, 'lucas')
console.log(person)
// {1: 1}

测试

//使用create代替new
function Person() {...}
// 使用内置函数new
var person = new Person(1,2)

// 使用手写的new,即create
var person = create(Person, 1,2)

new 被调用后大致做了哪几件事情

  • 让实例可以访问到私有属性;
  • 让实例可以访问构造函数原型(constructor.prototype)所在原型链上的属性;
  • 构造函数返回的最后结果是引用数据类型。
### Vue.js 响应式系统实现原理 Vue.js 的响应式系统基于 JavaScript 的 `Object.defineProperty` 方法(在 Vue 2 中)和 Proxy 对象(在 Vue 3 中)。以下是其实现的关键点: #### 1. **Vue 2 响应式原理** 在 Vue 2 中,响应式的实现依赖于 `Object.defineProperty` 或其改进版 `Object.defineProperties`。这些方法允许开发者拦截对象属性的读取 (`get`) 和设置 (`set`) 操作。 - 当创建一个 Vue 实例时,框架会对实例上的数据对象进行递归遍历,并使用 `Object.defineProperty` 将每个属性定义为 getter 和 setter[^1]。 ```javascript function observe(obj) { if (typeof obj !== 'object' || obj === null) return; Object.keys(obj).forEach(key => { defineReactive(obj, key, obj[key]); }); } function defineReactive(obj, key, val) { const dep = new Dep(); // 创建订阅器 let childOb = observe(val); // 如果子属性也是对象,则继续观察 Object.defineProperty(obj, key, { enumerable: true, configurable: true, get() { console.log(`获取 ${key}`); Dep.target && dep.addSub(Dep.target); return val; }, set(newVal) { if (newVal === val) return; console.log(`${key} 已更新`); val = newVal; dep.notify(); } }); } ``` 上述代码展示了如何通过 `defineProperty` 来监听对象的变化并通知订阅者[^4]。 --- #### 2. **Vue 3 响应式原理** Vue 3 使用更现代的 Proxy 替代了 `Object.defineProperty`,解决了后者的一些局限性,比如无法检测数组变化或新增/删除属性等问题。 - 在 Vue 3 中,Proxy 提供了一个全局的对象拦截机制,可以捕获更多类型的访问行为,如 `get`、`set`、`has` 等。 ```javascript const reactiveHandler = { get(target, key, receiver) { track(target, key); // 收集依赖 return Reflect.get(target, key, receiver); }, set(target, key, value, receiver) { const oldValue = target[key]; const result = Reflect.set(target, key, value, receiver); if (oldValue !== value) { trigger(target, key); // 触发更新 } return result; } }; export function reactive(obj) { return new Proxy(obj, reactiveHandler); } ``` 这段代码说明了 Vue 3 如何利用 Proxy 动态追踪和触发依赖关系[^5]。 --- #### 3. **双向绑定事件驱动** Vue.js 还支持双向绑定功能,这主要依靠自定义指令 `v-model` 完成。当用户输入发生变化时,会触发 DOM 事件(如 `input`),进而同步修改绑定的数据;反之亦然,当数据被程序化更改时,视图也会相应刷新[^3]。 --- ### 面试题总结 以下是关于 Vue 响应式系统的常见面试问题及相关解答: 1. **什么是 Vue 的响应式?它是如何工作的?** - Vue 的响应式是指当数据模型发生变更时,视图能够自动更新。这种能力由底层的观测机制提供,在 Vue 2 中采用的是 `Object.defineProperty`,而在 Vue 3 则升级为了 Proxy。 2. **为什么 Vue 2 不支持直接监测数组的变化?** - 因为 `Object.defineProperty` 只能劫持已存在的键值对,对于数组的操作(如 push/pop/unshift/splice 等),需要重写原生方法才能捕捉到它们的行为。 3. **Vue 3 的响应式相比 Vue 2 有哪些优势?** - Vue 3 使用 Proxy 解决了许多 Vue 2 存在的问题,例如无需手动处理动态添加属性的情况,也简化了深层嵌套结构的监控逻辑。 4. **谈谈你对 Vue 数据流的理解?** - Vue 是单向数据流设计模式的一个典型例子。父组件传递 props 给子组件,而子组件则通过 emit 向上发送事件消息给父级。这种方式有助于保持状态管理清晰明了。 5. **如果想让某个变量不参响应式怎么办?** - 可以将其标记为静态字段或者借助 Vue 提供的方法 `$options.data()` 返回的结果之外单独声明该变量。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值