JavaScript 自定义对象 及 new()原理与实现 如何完整地手写实现new

简介: JavaScript 自定义对象 及 new()原理与实现 如何完整地手写实现new

JavaScript 自定义对象

new() 原理与实现


object 表示类型,是 JavaScript 的六种 主要类型 之一(ES6后新增了Symbol为7种) ,也就是我们所说的 “对象” ,用于存储各种键值集合和更复杂的实体。Object()(首字母大写)是JavaScript语言中提供的用于创建 object 的构造器,相当于一些语言中的 Object 类,也就是说在 JavaScript 中所有的对象都是 Object "类"的实例。

使用new是运算符用于创建对象的方式之一。

1. JavaScript中自定义对象的方法

1.1 直接创建

在一对花括号中填写若干个属性名和属性值的方法可以用于直接创建对象,这是最简单也最直观的方法,其格式为:

var objName = {
    attribName1: attribvalue1, 
    attribName2: attribvalue2, 
    ...
  }

例如:

var student = {
  name: '李华',
  age: 12,
  sex: 'male'
}

1.2 通过 Object 对象创建

Object是JavaScript内置对象构造器,

obj = new Object([value])

例如:

var student = new Object();
student.name = '李华';
student.age = 12;
student.sex = 'male';

这与 1.1 节例子中创建的对象是一样的。

1.3 通过自定义构造函数创建

以下这种方式只能在非严格模式下成立:

// 1. 定义作为`构造函数`的函数
function Teacher(name, sex, height){
  this.name = name;
  this.sex = sex;
  this.height = height;
}
// 创建对象实例
var teacher1 = new Teacher("ZhangSan", "male", 190);

1.4 关于 this 的指向的说明

1. 在全局执行环境中(在任何函数体外部)this 都指向全局对象。在浏览器中, window 对象同时也是全局对象。

【注意】在严格模式下将有所不同,函数内部的this会若无赋值,将一直保持为undefined

2. 在函数内部,this的值取决于函数被调用的方式

1.3 节中,如果直接调用Teacher函数即执行("ZhangSan", "male", 190)this将指向window对象(非严格模式)。然而将其作为构造器使用时,即执行new Teacher("ZhangSan", "male", 190)时,new运算符改变了this的指向,使其不再指向window,而是指向新创建的对象。这在之后的自己实现 new章节的代码中将会看到具体的过程。

2. new 运算符在创建自定义对象的过程

new运算符创建一个用户定义的对象类型的实例具有构造函数的内置对象的实例。具体来说,它做了以下工作:

  • (1)创建一个空的简单JavaScript对象(即{});
  • (2)为步骤1新创建的对象添加属性__proto__,将该属性链接至构造函数的原型对象
  • (3)将(1)中新创建的对象作为this的上下文 ;
  • (4)如果该函数没有返回对象,则返回this

Object.prototype.__proto__已经从 Web 标准中删除,虽然一些浏览器目前仍然支持它,但也许会在未来的某个时间停止支持,请尽量不要使用该特性。通过现代浏览器的操作属性的便利性,可以改变一个对象的 [[Prototype]] 属性, 这种行为在每一个JavaScript引擎和浏览器中都是一个非常慢且影响性能的操作,使用这种方式来改变和继承属性是对性能影响非常严重的,并且性能消耗的时间也不是简单的花费在 obj.proto = … 语句上, 它还会影响到所有继承来自该 [[Prototype]] 的对象,如果你关心性能,你就不应该在一个对象中修改它的 [[Prototype]]。

3. 自己实现一个拥有new功能的函数

3.1 完整手写实现

为了完整体现new的过程,这里禁止调用Object.create()Function.prototype.apply()这两个方法,完整地按照2 小节中的步骤进行实现。

function myNew(cstrct,...params){
  // 1.创建一个空的JavaScript对象 obj
  const obj = {};
  // 2.为新创建的对象(obj)添加属性`__proto__`,
  // 并将该属性 链接至 构造函数的 原型对象  ;
  obj.__proto__ = cstrct.prototype; 
  // 3. 将该新对象(obj)作为this的上下文 ;
  // 3.1 在 obj.__proto__ 上临时挂上构造函数
  obj.__proto__._func = cstrct;
  // 3.2 执行该构造函数得到 "this"
  let _ = obj._func(...params);
  // 3.3 删除该临时挂载的属性 _func(否则实例上会多出这个属性)
  delete obj.__proto__._func
  // 4. 如果该函数没有返回对象,即null或undefined,
  // 则返回this,否则返回该对象
  return _ instanceof Object ? _ : obj;
}

3.2 调用现成方法实现

了解了完整地实现过程之后,再介绍两个方法:

方法 语法 描述
Object.create() Object.create(proto,[propertiesObject]) 使用指定的原型对象和属性创建一个新对象。
Function.prototype.apply() func.apply(thisArg, [argsArray]) 用于调用一个具有给定this值的函数,以及以一个数组(或类数组对象)的形式提供的参数。
返回 调用有指定this值和参数的函数的结果。

因此3.1 节中的

function myNew(cstrct,...params){
  const obj = Object.create(cstrct.prototype)
  obj.__proto__._func = cstrct;
  // 3.2 执行该构造函数得到
  let _ = obj._func(...params);
  // 删除该临时挂载的属性 _func(否则实例上会多出这个属性)
  delete obj.__proto__._func
  return _ instanceof Object ? _ : obj;
}

4. 使用 new 创建 类(class) 实例

首先很遗憾地说,JavaScript 并不是基于 的面向对象编程语言,即使在有了class关键字后,本质上还是基于原型链的,而class只是为了模仿其它语言中的类而新增的语法糖。ES6引入了也就是class,其本质上是一个JavaScript 函数

例如:

class Car{
  public color;
  public height;
  constructor(color,height){
    this.color = color;
    this.height = height;
  }
  run(){
    console.log('I am running at high speed...');
  }
  serfIntroduce(){
    console.log(`I am a ${this.color} car with a height of ${this.height.toString()} meters.`)
  }
}
const lamborghini = new Car('green',1.05)

实际上上面的代码只是以下代码使用class语法糖的结果:

var Car = (function () {
    function Car(color, height) {
        this.color = color;
        this.height = height;
    }
    Car.prototype.run = function () {
        console.log('I am running at high speed...');
    };
    Car.prototype.serfIntroduce = function () {
        console.log("I am a ".concat(this.color, " car with a height of ").concat(this.height.toString(), " meters."));
    };
    return Car;
}());
var lamborghini = new Car('green', 1.05);
lamborghini.serfIntroduce();
lamborghini.run();

从以上代码可以看出虽然自从ES6标准发布后在形式上看JavaScript中的"类"看起来就像 Java 等语言中的类用法一样,但本质上JavaScript中的new只是模拟了Java等语言中new的行为。

目录
相关文章
|
18天前
|
JSON JavaScript Linux
【MCP教程系列】Node.js+TypeScript搭建NPX MCP服务并自定义部署至阿里云百炼
本文介绍如何将阿里云百炼的工作流封装成MCP服务并部署,随后引入到智能体中使用。主要步骤包括:1) 封装MCP服务;2) 发布到npm官方平台;3) 在阿里云百炼平台创建自定义MCP服务;4) 在智能体中添加自定义MCP服务。通过这些步骤,用户可以轻松将工作流转化为MCP服务,并在智能体中调用。
【MCP教程系列】Node.js+TypeScript搭建NPX MCP服务并自定义部署至阿里云百炼
|
10天前
|
机器学习/深度学习 JavaScript 前端开发
JS进阶教程:递归函数原理与篇例解析
通过对这些代码示例的学习,我们已经了解了递归的原理以及递归在JS中的应用方法。递归虽然有着理论升华,但弄清它的核心思想并不难。举个随手可见的例子,火影鸣人做的影分身,你看到的都是同一个鸣人,但他们的行为却能在全局产生影响,这不就是递归吗?雾里看花,透过其间你或许已经深入了递归的魅力之中。
56 19
|
2月前
|
编解码 JavaScript 前端开发
【Java进阶】详解JavaScript的BOM(浏览器对象模型)
总的来说,BOM提供了一种方式来与浏览器进行交互。通过BOM,你可以操作窗口、获取URL、操作历史、访问HTML文档、获取浏览器信息和屏幕信息等。虽然BOM并没有正式的标准,但大多数现代浏览器都实现了相似的功能,因此,你可以放心地在你的JavaScript代码中使用BOM。
92 23
|
3月前
|
JavaScript 前端开发 Java
深入理解 JavaScript 中的 Array.find() 方法:原理、性能优势与实用案例详解
Array.find() 是 JavaScript 数组方法中一个非常实用和强大的工具。它不仅提供了简洁的查找操作,还具有性能上的独特优势:返回的引用能够直接影响原数组的数据内容,使得数据更新更加高效。通过各种场景的展示,我们可以看到 Array.find() 在更新、条件查找和嵌套结构查找等场景中的广泛应用。 在实际开发中,掌握 Array.find() 的特性和使用技巧,可以让代码更加简洁高效,特别是在需要直接修改原数据内容的情形。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一
|
3月前
|
监控 JavaScript 前端开发
MutationObserver详解+案例——深入理解 JavaScript 中的 MutationObserver:原理与实战案例
MutationObserver 是一个非常强大的 API,提供了一种高效、灵活的方式来监听和响应 DOM 变化。它解决了传统 DOM 事件监听器的诸多局限性,通过异步、批量的方式处理 DOM 变化,大大提高了性能和效率。在实际开发中,合理使用 MutationObserver 可以帮助我们更好地控制 DOM 操作,提高代码的健壮性和可维护性。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
MutationObserver详解+案例——深入理解 JavaScript 中的 MutationObserver:原理与实战案例
|
3月前
|
JavaScript 前端开发 Java
详解js柯里化原理及用法,探究柯里化在Redux Selector 的场景模拟、构建复杂的数据流管道、优化深度嵌套函数中的精妙应用
柯里化是一种强大的函数式编程技术,它通过将函数分解为单参数形式,实现了灵活性与可复用性的统一。无论是参数复用、延迟执行,还是函数组合,柯里化都为现代编程提供了极大的便利。 从 Redux 的选择器优化到复杂的数据流处理,再到深度嵌套的函数优化,柯里化在实际开发中展现出了非凡的价值。如果你希望编写更简洁、更优雅的代码,柯里化无疑是一个值得深入学习和实践的工具。从简单的实现到复杂的应用,希望这篇博客能为你揭开柯里化的奥秘,助力你的开发之旅! 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一
|
7月前
|
JSON 前端开发 JavaScript
JavaScript中对象的数据拷贝
本文介绍了JavaScript中对象数据拷贝的问题及解决方案。作者首先解释了对象赋值时地址共享导致的值同步变化现象,随后提供了五种解决方法:手动复制、`Object.assign`、扩展运算符、`JSON.stringify`与`JSON.parse`组合以及自定义深拷贝函数。每种方法都有其适用场景和局限性,文章最后鼓励读者关注作者以获取更多前端知识分享。
76 1
JavaScript中对象的数据拷贝
|
7月前
|
JavaScript 前端开发 API
Vue.js响应式原理深度解析:从Vue 2到Vue 3的演进
Vue.js响应式原理深度解析:从Vue 2到Vue 3的演进
204 17
|
7月前
|
缓存 前端开发 JavaScript
JavaScript前端路由的实现原理及其在单页应用中的重要性,涵盖前端路由概念、基本原理、常见实现方式
本文深入解析了JavaScript前端路由的实现原理及其在单页应用中的重要性,涵盖前端路由概念、基本原理、常见实现方式(Hash路由和History路由)、优点及挑战,并通过实际案例分析,帮助开发者更好地理解和应用这一关键技术,提升用户体验。
243 1
|
7月前
|
监控 JavaScript 算法
深度剖析 Vue.js 响应式原理:从数据劫持到视图更新的全流程详解
本文深入解析Vue.js的响应式机制,从数据劫持到视图更新的全过程,详细讲解了其实现原理和运作流程。