首先我们都知道 JavaScript 的数据类型有:
1. 原始类型: string, number, boolean, undefined, null, symbol, bigInt
2. 引用类型: object, array, function
然后我们要知道,在 JavaScript 中万物皆对象。这句话要怎么理解呢?我们先来了解创建对象的方式。
创建对象的方式
- 字面量,也就是我们最常用的:
const obj = { // 对象字面量
name: 'ikun'
}
其实在创建对象字面量时,v8 引擎也是偷偷执行了 const obj = new Object()
- new Object()
const obj = new Object() // 构造函数创建
obj.name = 'ikun'
3. new 一个构造函数
function Person(name){
this.name = name
}
const obj = new Person('ikun')
函数有二义性:可以普通调用,也可以 new 调用
然后我们来搞清楚 new 的工作原理。
new 的工作原理
- new 会往函数内凭空创造一个 this 对象
- 执行函数中的代码
- 让这个对象的原型等于函数的原型,即 this.__ proto __ = Xxx.prototype
- return 这个对象
function Car(color) {
// var this = {}
this.name = 'su7'
this.height = 1400
this.lang = 4800
this.weight = 1500
this.color = color
// return this
}
然后我们来说说 包装类
包装类
在了解包装类之前,我们要先知道两条铁律:
- 原始类型是不能添加属性和方法的
- v8 在对象中查找属性,如果找不到,一定会去它的原型上找
然后我们来讲包装类
- 当用户创建一个字面量, v8 会默认执行成 new Xxx()
const str = 'hello' // 字符串字面量 const str = new String('hello')
2. 因为原始类型不能增加属性和方法,所以, v8 在 new 出这个实例对象后,立即会做一个拆箱操作
const str = 'hello' // 字符串字面量 const str = new String('hello')
str.name = 'kunkun' // str.name = 'kunkun'
// delete str.name
// str.[[PrimitiveValue]]
console.log(str.name);
注释中的是 v8 引擎进行的操作
所以使用 str.name 这个方法后,这份代码不会报错,而是输出 undefined。
最后我们来说明 原型
原型
// 原型
// 所有的 函数 都天生拥有一个属性叫 prototype
// 对象 天生拥有一个属性 叫 __proto__
function Car(color) {
// var this = {}
this.name = 'su7'
// this.__proto__ = Car.prototype
}
const car = new Car()
console.log(car.tostring);
原型链有个查找规则:当前对象查找 → 原型查找 → 原型的原型 → ... → null。
car.tostring 是已经被写在 prototype 中的,所以这份代码能够运行并输出结果。
那么我们来看下面这份代码:
const str = 'hello' //const strObj = new String('hello')
str.length = 4 // this.__proto__ = String.prototype
console.log(str.length); // strObj.__proto__.lengths
输出结果并不是 4 ,因为原始类型不能增加属性和方法,所以输出的是原型上的 length:5 。 那么如果我们想要自己增加一个方法要怎么操作呢?我们可以:
String.prototype.len = 4
const str = 'hello'
console.log(str.len);
诸如数组中的 arr.push 、arr.unshift 等都是在原型中查找到的。
总结
在阅读完本文后我们就能清楚明白为什么 万物皆对象。
我们也明白了为什么 length 等的方法能直接使用: str.length, length是 String 函数原型上的属性,并不是给字符串增加的属性。