写在前面
最近工作太忙, 快接近两周没更新博客, 总感觉有一些事情等着自己去做, 虽然工作内容对自己提升挺大, 但我总觉得, 一直埋着头走路, 偶尔也需要抬起头来, 看看现在和自己的期望向是否脱轨, 所以周末还是选择来星巴克写些文字.
今天记录 JavaScript 中 new 关键字的模拟实现, 当我们在模拟实现某个语言行为之前, 应该想想这个行为都做了哪些事情, 通过实践, 最后也能更加掌握知识点, 这就是很多面试题都会问到模拟实现的原因, 目的是为了考察候选人知识的深度.
- function Person(name) {
- this.name = name;
- }
- var person = new Person('jayChou');
- typeof(person) // "object"
- person instanceof Person // true
- person.__proto__ === Person.prototype // true
- person.constructor === Person // true
- person.constructor === Person.prototype.constructor // true
以上, 可以看出:
new 创建并返回了一个新对象, 是构造函数的实例
对象的实例的构造函数属性其实是构造函数的原型对象的 constructor 属性
对象实例的 __proto__ 关联到构造函数的原型对象
上面的内容有关于 JavaScript 中原型对象和原型链的知识, 不够清楚的同学可以查看我之前的博客.
由于 new 是 JS 的一个关键字, 我们无法实现关键字, 但我们可以通过函数的形式来模拟 new 关键字的行为.
一, 基本思路
知道 new 关键字做了哪些工作, 那我们就有了模拟实现的基本思路.
- /**
- * 模拟实现 JavaScript new 操作符
- * @param {Function} constructor [构造函数]
- * @return {Object|Function|Regex|Date|Error} [返回结果]
- */
- function mockNew() {
- // 创建一个空对象
- let resultObj = new Object();
- // 取传入的第一个参数, 即构造函数, 并删除第一个参数.
- // 关于为什么要用 Array.prototype.shift.call 的形式, 见之前的博客文章 《JavaScript 之 arguments》
- let constructor = Array.prototype.shift.call(arguments);
- // 类型判断, 错误处理
- if(typeof constructor !== "function") {
- throw("构造函数第一个参数应为函数");
- }
- // 绑定 constructor 属性
- resultObj.constructor = constructor;
- // 关联 __proto__ 到 constructor.prototype
- resultObj.__proto__ = constructor.prototype;
- // 将构造函数的 this 指向返回的对象
- constructor.apply(resultObj, arguments);
- // 返回对象
- return resultObj;
- }
- function Person(name) {
- this.name = name;
- }
- var person = mockNew(Person, "jayChou");
- console.log(person);
- // constructor: ƒ Person(name)
- // name: "jayChou"
- // __proto__: Object
基本思路正确! 所以我们完成了 new 关键字的初步模拟. 伙伴们可以自己动手敲一下, 每句代码自己是否都能理解.
二, 处理返回值
构造函数也是函数, 有不同类型返回值. 有时候构造函数会返回指定的对象内容, 所以要对这部分进行处理.
- /**
- * 模拟实现 JavaScript new 操作符
- * @param {Function} constructor [构造函数]
- * @return {Object|Function|Regex|Date|Error} [返回结果]
- */
- function mockNew() {
- // 创建一个空对象
- let emptyObj = new Object();
- // 取传入的第一个参数, 即构造函数, 并删除第一个参数.
- // 关于为什么要用 Array.prototype.shift.call 的形式, 见之前的博客文章 《JavaScript 之 arguments》
- let constructor = Array.prototype.shift.call(arguments);
- // 类型判断, 错误处理
- if(typeof constructor !== "function") {
- throw("构造函数第一个参数应为函数");
- }
- // 绑定 constructor 属性
- emptyObj.constructor = constructor;
- // 关联 __proto__ 到 constructor.prototype
- emptyObj.__proto__ = constructor.prototype;
- // 将构造函数的 this 指向返回的对象
- let resultObj = constructor.apply(emptyObj, arguments);
- // 返回类型判断, 如果是对象, 则返回构造函数返回的对象
- if (typeof resultObj === "object") {
- return resultObj
- }
- // 返回对象
- return emptyObj;
- }
- function Person(name) {
- this.name = name;
- return {
- name: this.name,
- age: 40
- }
- }
- var person = mockNew(Person, "jayChou");
- console.log(person);
- // {name: "jayChou", age: 40}
- // age: 40
- // name: "jayChou"
- // __proto__: Object
当返回值返回了一个自定义对象后, 模拟 new 函数就返回该自定义对象.
总结
JavaScript new 关键字的意义在于让普通函数生成一个新对象, 并将对象实例的 __proto__ 关联到函数的 prototype 对象.
本文中有些地方需要一些前置知识, 但是总体上理解是比较容易的. 如果有迷惑的地方, 可以翻看我之前的博客文章
来源: https://juejin.im/post/5be6e44ee51d4532a326b672