Code前端首页关于Code前端联系我们

在 JavaScript 中使用 new 连接数据点

terry 2年前 (2023-09-07) 阅读数 155 #Vue

这篇文章将帮助你学习 |检查以下数据点:

  • 使用

    new 运算符

  • 数据类型、原始值

  • 内置构建器、自定义构建器

  • 原型、原型链

  • ES6 级

新干员

new 用于实例化具有构造函数的对象类型。语法如下:

new Constructor[([args])];
 

其中,Constructor为构造函数,构造函数参数args为可选;当没有args时,new Constructornew Constructor()是等价的。

执行new时,它基本上执行以下操作:

  1. 创建一个空对象{}

  2. __proto__Constructor.prototype(实现类型继承,构建原型链)

  3. 在构造函数中使用创建的对象作为this,并执行构造函数

  4. 构造函数返回构造函数内部的this,不带

    ,这是new运算符的结果(也可以在创建阶段主动返回对象,但不建议重写构造函数中的普通对象 - TypeScript 明确要求构造函数的返回类型必须为 void)

因此,如果 new 用于空函数,则返回空对象 {}

new (function() {}); // {}
 

对于构建者来说,执行new Constructor()Constructor()得到的结果通常是不同的:

typeof Date(); // 'string'
typeof new Date(); // 'object' 
 

new 除了使用构造函数之外,您还可以对 ES6 中的类执行操作。但 ES6 类只是语法糖,本质上还是构造函数,所以我们重点关注构造函数

内置构建器

内置的构造函数有很多,在日常生活中也经常使用:Date()

Map()

// 构造函数声明式
function Phone(make, model) {
   this.make = make;
   this.model = model;
}
Phone.prototype.logMake = function() {
  console.log('当前手机的厂商:', this.make);
}
 
、…

在 JavaScript 中,构造函数通常使用驼峰命名法命名。

我们可以使用构造函数名称作为构造函数产生的实例的类型,例如new Date()类型是Date,new Error('出错了出错了啊')类型是Error,类型是Array,♷Object,100类型是 Number,'nice !' 类型是 String,true 类型是 Boolean...构造函数的名称我们通过 获取。 (稍后将使用它来确定任何 对象的类型)

数据类型对应的构造函数

八种主要数据类型中,除了nullundefined外,其他六种数据类型都有对应的构造函数,分别是:❀

  • Object()

  • Boolean()

  • Number()

  • String()

  • Symbol()

  • BigInt()

最后两个Symbol()BigInt()是ES6中新的基本类型。他们不能使用 new ,只能通过调用构造函数来获取原始值,例如: const typeSymbol = Symbol('type');

不能使用 new 的原因是 ES6 规定 new 不能使用基类型构造函数执行。并且Boolean()Number()String()也支持new功能,更多是出于兼容性原因。现在建议使用文字来创建原始值。

如果您确实想获取包裹在(包含条目[[PrimitiveValue]]的对象)中的的原始值,您可以使用构造函数并调用方法来获取原始值:

image.pngimage.png

new Object()Object()的效果几乎相同。

在 JavaScript 中,当我们说“对象”或“对象”时,我们通常指的是包含键值对的实例;当我们说“对象”时,我们通常指的是构造函数Object()

原始值包装

例如

运行

image_1.pngimage_1.png

打印在控制台上的 Boolean {false}

是一个原始值包装器,具有两个属性:

  • 是一个继承自Boolean.prototype的对象(这个对象继承自Object.prototype),所以如果运行new Boolean(false) && 100,返回的是
  • 它包含值 void PrimitiveValue

如果只执行构造函数而不使用运算符new,则仅返回原始值(必要时转换数据):

image_2.pngimage_2.png

基本类型

七种基本类型:

  • 空:null

  • 未指定:undefined

  • 布尔值:true false

  • 数量:1 100

  • 字符串:'hello' '你好'

  • 符号:Symbol.toStringTag

  • BigInt: …

基本类型代表语言实现的最低级别。

基本类型

的值称为原始值,原始数据如 nullundefinedtrue、、 。

随着我们的日常开发,我们注意到基本类型在代码中被广泛使用,因此在设计JavaScript语言时,基本类型必须保证

为了实现这个目标,原始值具有以下属性:

  • 存储在堆栈中(复杂类型的引用值存储在堆栈中)

  • 不变

  • 没有属性或方法

除了

JavaScript原始值(基本类型的值)之外,其他都是引用值(复杂类型的值,继承自Object.prototype),函数也是引用值,只不过里面是函数是[[callable]],可以执行()语法。

原值不变

let a = 1;
((num) => num++)(a);
a; // 1 
 

由于原始值的不可变性,当原始值作为函数参数传递时,它实际上是原始传递值的副本。函数内部工作的副本不会以任何方式影响原始值。 。

但是如果函数接收到一个参考值,它本身会随着函数体一起改变:

let arr = [];
((value) => value.push('Oh!'))(arr);
arr; // ["Oh!"]
 

初始值没有属性或方法

上面提到,为了保证效率,原始值没有属性和方法,但我们经常做以下操作:

const str = 'abc';
str.substr(-1); // 'c'
str.length; // 3 
 

为什么可以调用属性length并调用方法.substr()

这是因为当使用原始值str调用方法和属性时,JavaScript引擎实际上会根据原始值创建一个对应的原始值包装器,即new String(str),然后调用这个包装器中的方法和属性。 。

同时,由于原值的不可变性,原值的包装器调用的所有方法,如.substr().substring().toFixed()等,都不会改变原值价值。函数运算的结果作为一个全新的原始值 返回 - 这是所有原始值的属性。


现在

讲完了new在JavaScript内置构造函数中的使用,我们来看看它在自定义构造函数中的应用。

定制构建器

// 定义对象类型:Phone
function Phone(make, model) {
  this.make = make;
  this.model = model;
}
 

执行new Phone('Apple', 'iPhone 12')控制台输出:

image_3.pngimage_3.png

这里我们创建了一个Phone类型的实例,属性makemodel在构造函数执行时正常设置。

另外,这个实例还有属性__proto__,它是什么?

__proto__

new获得的每个实例都有一个属性__proto__,该属性仅用于一件事:指向当前实例的原型(父类),即实例的❙❙❙❙ 。在上面的示例中,它显示 Phone.prototype。对于使用对象字面量创建的对象,它指向Object.prototype;对于使用数组文字创建的对象,它指向 Array.prototype,对于使用字符串文字创建的原始值,它指向 String.prototype

我们可以通过更改 __proto__ 来更改当前实例的原型,前提是该对象必须通过 Object.isExtensible() 评估为可扩展。要更改的值必须是一个对象或null

出于性能原因,不再建议使用__proto__。如果你使用obj.__proto__ = ...,很可能会出现问题!现在更推荐使用Object.getPrototypeOf(o)/Object.setPrototypeOf(o, proto)

那么__proto__prototype 之间有什么关系呢?

prototype——原型

首先我们来看看prototype属性出现在哪些对象中?

答案:内置构造函数函数和自定义普通函数

image_4.pngimage_4.png

箭头功能没有prototype

image_5.pngimage_5.png

也没有出现在prototype

image_6.pngimage_6.png

相应地,__proto__ 出现在对象实例中。

很多时候我们注意到原型也有__proto__。这是因为原型也是其他原型的实例。如果没有多级继承,通常是Object.prototype

Number.prototype.__proto__ === Object.prototype; // true
 

原型的两个基本特征

“纯粹”Constructor.prototype有两个特点:

  • constructor – 指向构建者Constructor

  • __proto__ – 原型Constructor.prototype __proto__通常指Object.prototype

constructor__proto__prototype的关系如图:

image_7.pngimage_7.png

JavaScript 除了 __proto__ 是一个空对象之外,所有其他对象都是 Object 的实例,并继承 Object.prototype 的属性和方法 - 尽管它们可以被覆盖。

有时会故意创建不具有典型原型链继承的对象,例如通过Object.create(null)创建的对象,或者通过obj.__proto__ = ...Object.setPrototypeOf(obj, proto)更改原型链。

更改

Object 原型会更改原型链中的所有对象,为扩展对象行为提供了非常强大的机制。下面的代码扩展了

,这样我们就可以轻松获取任何程序中任何对象的数据类型:
Object.defineProperty(Object.prototype, Symbol.type = Symbol('type'), {
  get() {
    // 规定 NaN 的类型为 'NaN',而不是 'Number'
    if (
      this.__proto__.constructor.name === 'Number' &&
      Number.isNaN(this.valueOf())
    ) {
      return 'NaN';
    }
    return this.__proto__.constructor.name;
  }
});
 

之后,除了nullundefined之外的所有基本类型数据和复杂类型数据都可以通过调用[Symbol.type]属性获取:

image_8.pngimage_8.png

在自定义构造函数 中应用

prototype
function Phone(make, model) {
  this.make = make;
  this.model = model;
  this.innerLogMake = function() {
    console.log('当前手机的厂商:', this.make);
  }
}

Phone.prototype.outerLogMake = function() {
    console.log('当前手机的厂商:', this.make);
}

const phone = new Phone('Apple', 'iPhone');

phone.innerLogMake(); // '当前手机的厂商: Apple'
phone.outerLogMake(); // '当前手机的厂商: Apple'
 

打印Phone.prototype

image_9.pngimage_9.png

outerLogMake 附加到 Phone 原型,因此实例可以沿着原型链调用此方法。

innerLogMake在构造函数内部,它实际上被认为是实例的属性,而不是方法。出于性能原因,该方法应坚持Phone.prototype,而不是每次执行构造函数时都重新创建该方法。

构造函数箭头函数和原型的区别

function Phone(make, model) {
  this.make = make;
  this.model = model;

  this.innerLogMake_arrow = () => {
    console.log('当前手机的厂商:', this.make);
  }
}

Phone.prototype.outerLogMake_arrow = () => {
      // 这里的 this 不指向 Phone 实例!!!
    console.log('当前手机的厂商:', this.make);
}

const phone = new Phone('Apple', 'iPhone');

phone.innerLogMake_arrow(); // '当前手机的厂商: Apple'
phone.outerLogMake_arrow(); // '当前手机的厂商: undefined'
 

修改实例原型

实例和原型之间的连接由实例的 __proto__ 字符指示。根据上面的内容,如果我们需要更改实例原型,我们应该调用Object.setPrototypeOf(o, proto),而不是直接指定__proto__

Object.setPrototypeOf(phone, null);
typeof phone.outerLogMake; // undefined
Object.setPrototypeOf(phone, Phone.prototype);
phone.outerLogMake(); // '当前手机的厂商: Apple'
 

实现继承:

function Phone(make, model) {
  this.make = make;
  this.model = model;
}
Phone.prototype.logMake = function() {
    console.log('当前手机的厂商:', this.make);
}

function HuaweiPhone(model) {
  // 父类的构造函数必须执行一次!
  Phone.call(this, '华为', model); // *
}
Object.setPrototypeOf(HuaweiPhone.prototype, Phone.prototype); // *

const p40 = new HuaweiPhone('P40');

p40.logMake(); // '当前手机的厂商: 华为'
 

打印p40

image_10.pngimage_10.png

默认情况下,HuaweiPhone.prototype.__proto__Object.prototype,实现Phone.prototype的继承关键两步:

  1. 在构造函数HuaweiPhone()(子类)中执行Phone()(父类),无论是call(),电话,因为实例类型可以是森林的属性,或。右
  2. HuaweiPhone.prototype设置为原型Phone.prototype以调用电话原型属性和方法

班级

首先,很明显JavaScript的Class只是语法糖。

语法糖:指添加到特定计算机语言中的语法。这个语法不影响语言功能,但是更方便程序员使用。 语法糖使程序更加简洁易读。 ——维基百科

JavaScript 类基本上是一个构造函数。下面使用类和构造函数来声明phone类型的数据对象:

// class 声明式
class Phone {
  constructor(make, model) {
    this.make = make;
    this.model = model;
  }
  
   logMake() {
     console.log('当前手机的厂商:', this.make);
   }
} 
 
// 构造函数声明式
function Phone(make, model) {
   this.make = make;
   this.model = model;
}
Phone.prototype.logMake = function() {
  console.log('当前手机的厂商:', this.make);
}
 

首先让我决定:它们是同一件事。

让我们运行实例代码,看看:

类声明对象类型并执行实例化:

可以清楚地看到,虽然Phone.prototype.constructor跟在class Phone后面,但它实际上是一个函数。所有功能它都有,最重要的是:

Phone.prototype.constructor.__proto__ === Function.prototype; // true 
 

假类,真功能,没跑!

让我们看一下定义对象类型并执行实例化的构造函数:

image_12.pngimage_12.png

可以看到,两个实例的输出内容几乎没有区别。

班级传承

class Phone {
  constructor(make, model) {
    this.make = make;
    this.model = model;
  }
  
   logMake() {
     console.log('当前手机的厂商:', this.make);
   }
}

// 使用关键字 extends 实现继承
class HuaweiPhone extends Phone {
  constructor(model) {
    // super 表示执行 Phone 中的 constructor(),必须调用!
    super('华为', model)
  }
} 
 

执行实例化:

image_13.pngimage_13.png


以上就是本文全部内容,如有错误还请指正!如果还有不明白的地方,可以留言!

参考文献

  • stackoverflow:为什么 javascript 中几乎所有东西都是对象

  • MDN:新运营商

  • MDN:Object.prototype.__proto__

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门