对象
- 在js中,万物皆对象
之前我们在 面试前过一遍:五分钟带你温习js的基本类型 梳理过,js类型有这样两种:
- 原始类型:
stringbooleanundefinedbigintnullsymbol - 引用类型:
objectarrayfunction
一、创建对象的方式
创建对象的方式有三种:
- 字面量
- new Object()
- new 一个构造函数
字面量
const obj = {
name : 'you'
}
obj.age = 18
console.log(obj)
其中,const obj{} 就是字面量创建对象。代码输出
{ name: 'qiqi' }
现在我们接着写这样一段代码:
let hello = 'world'
obj.hello = 123
obj[hello] = 123
现在你思考:3、4行代码意思一样吗?
显然不同。上面的意思是给对象添加了一个名为 hello 的属性值。
而下面的是把 hello 作为一个变量值来赋值为 123
new Object()
const obj = new Object()
obj.name = 'you'
console.log(obj)
在 js 中,已经封装好了 Object()函数。我们直接使用这个函数就可以创建对象。
事实上:
- V8 只要看见 {} 就会直接 new 一个对象
- 本质上 js 创建对象只有一种方式,那就是 new Object()
new 一个构造函数
function Person(){
}
const obj = new Person()
console.log(obj)
我们先创建一个函数 Person(),接着 new Person() 将其转化为对象。
函数有二义性: 可以普通调用,也可以 new 调用,并且会得到一个新对象
二、new 的工作原理
- new 会往函数内凭空创建一个 this 对象
- 执行函数中的代码
- 让这个对象的原型 等于 函数的原型
- return 这个对象
来一段代码感受一下:假设 z 要去买一辆车,你也要去买一辆车:
function Car(color) {
this.color = color
this.name = 'su7'
this.height = 1400
this.lang = 4800
this.weight = 1500
}
const zCar = new Car('red')
const yourCar = new Car('blue')
当我们执行 new Car('red') 时,V8引擎完成了四件事
- 创建一个空对象
var this = {} - 将
this指向这个空对象,并执行函数体中的代码
this.color = color
this.name = 'su7'
- 将这个对象的隐式原型
__proto__指向Car的显式原型prototype - 返回这个对象(默认
return this)
输出的是一个 car 实例,包含了 name、height、lang、weight等属性
那么如果我做这样的判断:
console.log(zCar === yourCar)
这俩对象能相等吗?你买的车显然和 z 的不是同一辆。因此会输出 false
引用类型比较的是内存地址:每次new都会创建一个全新的对象,分配在不同的内存地址。所以即使属性完全相同,两个对象也不相等。JS中不存在两个完全一样的对象
三、包装类
- 原始类型是不能添加属性和方法的
- V8在对象中查找属性,如果找不到,一定会去它的原型上找
这是一个经典面试题:
var str = 'abc'
str.length = 4
console.log(str.length)
会输出 3。为什么赋值了 4,读取时还是 3?背后发生了这样的事情:
包装类执行流程
var str = 'abc'
str.length = 4
console.log(str.length)
第 2 行 str.length = 4:
- V8 将原始字符串
abc自动装箱,临时包装为一个string对象,等价于new String('abc') - 在这个临时对象的
length属性上尝试赋值为 4 - 但
string对象的length属性是只读的,赋值静默失败 - 临时对象被销毁
第 3 行 console.log(str.length):
- 再次对原始值
abc访问length属性 - V8 又创建一个新的临时
String包装对象 - 读取
length属性,返回3 - 临时对象被销毁
包装类原理
- 当用户创建一个字面量,V8会默认之形成 new Xxxfunction()
- 因为原始类型不能增加属性和方法,所以,V8在new出这个实例对象后,立即会做一个拆箱操作(把刚加进去的属性移除掉)
- str.length length是 String 函数原型上的属性,并不是给字符串增加的属性
四、js 原型与原型链
- 函数的显式原型
prototype
- 所有函数都天生用有一个属性叫
prototype
- 对象的隐式原型
__proto__
- 所有对象都天生用有一个属性叫
prototype
const obj = {}
obj.toString()
const obj = {} 相当于 new Object()
- V8在
obj的原型上找toString,等同于构造函数的原型上找 obj.__proto__ == Object.prototype
原型查找链机制
任何对象最终都继承自 Object.prototype(原型为 null)
- 访问一个对象的属性时,V8查找顺序如下:
对象自身->对象.__proto__->...->Object.prototype->null
任何对象最终都继承自
Object.prototype,而Object.prototype的原型是null,查找到此结束
修改原型要谨慎
const str = 'hello'
我们定义一个字符串,这是原始类型,显然不能赋予属性值。如果我们非要修改其属性呢?例如
String.prototype.len = 5
const str = 'hello'
console.log(str.len)
是的,我们为原始类型添加属性和方法,但我们可以直接修改其原型来实现这个效果。
这也会带来几个问题
- 全局污染
- 之后的所有字符串都会继承这个属性
- 命名冲突
- 如果多个库都添加了同名的原型属性,后加载的会覆盖先加载的,引发难以排查的 bug
正确使用方法:使用独立函数
function getLen(s) {
return s.length
}
注意
-
写代码的时候,如果你创建了一个 需要使用的函数,那么这个函数名的首字母应当大写
-
完全一致属性的两个对象,但他们并不相等
-
引用类型比较是否相等,首先看引用地址是否相等
-
js中没有完全一样的两个对象,因为其引用地址不可能重复