JavaScript的对象、new的机制与原型包装类

原始字符串只是一个简单值,为什么能调用 .length?用 new 调用新函数能生成新对象,v8引擎究竟干了些什么?实例上找不到属性时,为什么会去构造函数身上找?想要明白这些内容,本篇文章应该会对你有所帮助,在开始本篇的学习前,要先记住,在JavaScript中,万物皆对象

一、简单回顾

1.原始基本类型

String、Number、Booleam、Null、Undefined、Symbol、BigInt等,都是原始基本类型,它们的特点都是存放在栈内存,只是单纯的值,不能新增自定义属性和方法

2.引用复杂类型

Object、Array、Function、Date等,都是引用复杂类型,它们的特点都是存放在堆内存,可以任意扩展属性、挂载方法。

那么问题来了:为什么字符串是原始类型,却可以调用方法,比如 str.length

二、创建对象的三种方法

1.对象字面量

对象字面量 复制代码
const obj = {  // 对象字面量
  name: '琪琪'
}
obj.age = 18
delete obj[hello]
obj.name = '迪哥'
console.log(obj);

我们常见的直接定义变量的方法就是使用对象字面量定义的方法,这种方法的优点就是简洁直观,缺点也很明显,当批量创建多个同类对象时,就会产生大量的重复代码。这里我提一下 ,有些人看到 const 定义了对象 obj 又写 obj.name = '迪哥' ,就会想,不是const 定义的是常量嘛?不能更改的吖,但是你再仔细想一想,在v8 的调用栈里,引用类型存储的内容是什么?是不是指向堆的地址?因为更改对象内的内容不会改变地址,所以更改内容并没有什么问题,除非你创建一个新的对象 赋给 obj ,比如 obj = {} 这就不允许了,由此可见引用类型比较比的是地址。

2.原生构造函数

原生构造函数 复制代码
const obj = new Object()   在v8眼里 const obj = {}  事实上就是  const obj = new Object()
obj.name = '迪哥'

我们把 new 创建出来的对象也称之为实例对象 ,在代码中没有提前进行函数声明的情况下, new Object() 就说明 Object() 是JavaScript自带的构造函数,同时也有 String() Number() 之类的函数,但是一定是JavaScript中有意义的类型 才会有构造函数,undefined就没有。须知,在v8引擎的眼里 const obj = {}const obj = new Object() 并无二致。

3.new一个自定义构造函数

new 复制代码
function Car(){

}
const MyCar = new Car()
console.log(MyCar); 

一般把构造函数的首字母大写,用于区分普通函数,由此我们也可以知道,函数具有二义性:普通调用只是执行代码,一旦加上 new ,必然会产出全新对象 。上述代码输出的内容就是一个空对象 {}

new 复制代码
function Car(){
    var i = 1;
    console.log(i);
    name:'lihua';
    age:18
}
const MyCar = new Car()
console.log(MyCar); 

那么加上一些内容,输出的结果又会是什么呢,实际上,属于键值对的部分会被放进 MyCar 这个实例对象内,而其他的代码会正常执行并输出,只不过不会在实例对象内,但是也不影响实例对象。new 的方法适合于批量创建实例的情景,复用构造函数代码,这就是构造函数存在的意义。

三、new 的工作原理

在文章开头抛出了一个问题,就是 str.length 到底怎么去理解它,在刚刚的讲解下,我们知道无论怎么定义变量,v8都会先将它定义为一个对象。

对象 复制代码
 let str = 'abcd'  //字符串字面量  v8 眼里就是 let str =  new String('abcd')

在v8眼里,无论你是定义一个num还是一个str,都会先 new 成一个对象,我们代入到v8的视角。

v8 复制代码
let str = new String('abcd')

这样str 就变成了一个实例对象,但是我们知道,原始类型就是原始类型,string就是原始类型,所以v8在最后还是会将string变回原始类型,那么new与v8究竟做了些什么呢?

1.凭空创建一个空对象

this 复制代码
function Car(){
    // var this = {}
    this.name = '奔驰'
    this.price = 1000000;
    this.color = '红色';
    this.type = '奔驰';
    // return this
}
const MyCar = new Car() //实例对象
console.log(MyCar);

用另一个例子来看,首先我们创建的这个构造函数 Car ,它在被 new 调用的时候会在函数内部生成一个空对象 this ,this可以先粗略的看做是这个新的实例对象。

2.执行函数中的代码

this 复制代码
function Car(){
    // var this = {}
    this.name = '奔驰'
    this.price = 1000000;
    this.color = '红色';
    this.type = '奔驰';
    // return this
}
const MyCar = new Car() //实例对象
console.log(MyCar);

如果函数中有 this.name = '奔驰' 之类的赋值语句,就会在实例对象内存入键值对 name: '奔驰'

3.打开原型链路

原型 复制代码
function Car(){
    // var this = {}
    this.name = '奔驰'
    this.price = 1000000;
    this.color = '红色';
    this.type = '奔驰';
    // return this
    // this.__proto__ = Car.prototype;
}

创建实例对象时,在函数体内会自动链接 this.__proto__ = Car.prototype; ,所有的函数都天生拥有一个属性叫 prototype ,这是一个对象,所有的对象同样有一个属性叫 proto,也是一个对象。也就是说这个实例对象的原型等于函数的原型,可以理解为继承,我们再看向str:

string 复制代码
let str = 'abc'     相当于    let str =  new String('abc')
str.name = '迪哥'
console.log(str);

回到str,new 是先创建了一个空对象,然后外围代码 str.name = '迪哥' ,但是始终破不了原始类型无法添加属性和方法,所以在输出str之前,v8会做出一个 delete str.name 的手段,那么最后 console.log(str); 输出的值是不是一个对象呢?不是的,特别的,对于原始类构建的对象中都会有一个 PrimitiveValue 原始值,原始值里面存储的就是原始类型的值 abc

图3.3-String创建的对象的内部内容

由图3.3我们得知 str.proto = String.prototype; ,并且 PrimitiveValue 也就是原始类型的值,当我们输出 console.log(str); 也就是在输出 str.\[PrimitiveValue] ,我们再看下面的代码。

原型 复制代码
let str =  new String('abc')
str.length = 4
console.log(str.length);

在图3.3中我们知道在这个str的实例对象里面确实是有一个 length ,是我们添加的属性,但是最后还是会被v8引擎给删除掉,那为什么还是有 str.length 呢?我们知道实例对象被创建后会有str.proto = String.prototype; ,所以当我们在查找对象中的属性,如果找不到时,一定会去它的原型上找。就是说,在str里面找不到 .length ,它会到String()这个构造函数里面找。

图3.4-String构造函数的内容

如图3.4,我们可以看到确实有一个 length:0 的键值对,最后我们找到了 str.length,但是我硬是要修改 str.length 的值怎么办?满足你,你就得:

原型 复制代码
let str =  new String('abc')
delete String.prototype.length
String.prototype.length = 190999;
console.log(str.length);

我们把创建的实例对象里的 length删除后再重新赋值新的length即可,由此可知,为什么会有 string.length,为什么说万物皆对象。

四、结语

若其中内容有误或过浅,欢迎大家批评指正,Ciallo~ (∠・ω< )⌒★

相关推荐
某鹏1 小时前
java伪共享问题的稳定解法
后端
weedsfly1 小时前
JavaScript 事件流:彻底搞懂捕获、冒泡与事件委托
前端·javascript·react.js
fliter1 小时前
Rust 不是手动内存管理:它是声明式内存管理
后端
AI人工智能_电脑小能手1 小时前
【大白话说Java面试题 第125题】【并发篇】第25题:说说 Java 线程的中断机制
java·后端·面试
fliter1 小时前
Box 里到底装了什么:从 Go interface 到 Rust trait object
后端
Java内核笔记1 小时前
Spring Security 源码解析(六)无状态 JWT 实践:Session 共享与自定义过滤器
java·后端
乘云数字DATABUFF1 小时前
5分钟部署开源APM Databuff:OpenTelemetry全链路追踪入门实战
运维·后端
杨利杰YJlio1 小时前
OpenClaw / clawdbot 是什么?看懂 Agent 体系
前端·后端
SamDeepThinking2 小时前
一条UPDATE语句在MySQL 8.0中到底加了几把锁?
后端·mysql·程序员