主要回顾对象的属性以及对象属性的遍历
对象属性
先说几个概念
- 键为字符串的属性,存放到properties对象中
- 键为数字的属性也叫排序属性,存放到elements对象中,键为数字字符串的属性也是排序属性,有序访问会快于字符串属性
- 对象内属性,不在properties对象中也不在elements对象中访问更快
设置一个添加属性的函数,然后我们去快照中观察属性
function CustomObj(eCount,pCount){
for(let i=0;i<eCount;i++){
this[i] = `e-${i}`;
}
for(let i=0;i<pCount;i++){
this[`p-${i}`] = `p-${i}`;
}
}
先生成12个属性
const obj = new CustomObj(6,6);
可以看到只有12个属性时,全部生成为对象内属性,并且没有生成properties对象,elements对象中存放的是排序属性;此时访问对象中的属性都是直接访问对象内属性会很快;并且elements对象中存储的属性也是有序的
生成24个属性
const obj = new CustomObj(12,12);
可以看到24个属性时,也是全部生成了对象内属性,出现了properties对象,此时elements对象和properties对象都是有序存放的,properties对象也是下标排序存储的
生成1000个属性
const obj = new CustomObj(100,100);
可以看到properties里面的属性已经不是排序存放的了,说明存储的数据结构发生了变化
总结
可以看到在字符串属性多的情况下,properties将不再是线性排序;线性数据访问使用下标访问非常快;但是添加和删除属性的时候效率就很低;非线性数据结构一般存储在字典中,虽然查找很慢但是插入和删除很快
在上面的截图中都可以看到map,在elements排序属性中是没有的;通常叫map是隐藏类,它描述的是对象的属性布局,因为有隐藏类,对象的访问比较快,在空间上也会减少消耗
- 添加删除属性都会破坏隐藏类,并且新生成的隐藏类和之前的隐藏类也有会联系,比如map中的back_pointer会指向之前的隐藏类
- 保护隐藏类:
- 一次性初始化完我们的属性
- 保持初始化属性的顺序,比如给obj赋值时第一次时name、age;第二次变成age、name
- 谨慎使用delete
创建属性
创建属性的方式有很多,这里主要讲一下Object.defineProperty,允许精确地添加或修改对象上的属性
- configurable:返回一个布尔值,决定了是否可以修改属性描述对象configurable为false时,value、writable、enumerable和configurable都不能被修改了, configurable修改成false是单向操作,无法撤销!以及无法被删除
- writable只有在false改为true会报错,true改为false是允许的
- 只要writable和configurable有一个为true,value就允许改动
- configurable为false时,直接目标属性赋值,不报错,但不会成功
- enumerable:返回一个布尔值,表示目标属性在 for…in、Object.keys、JSON.stringify 中是否可遍历;如果需要获取对象自身的所有属性,不管是否可遍历,可以使用Object.getOwnPropertyNames方法
- writable:是一个布尔值,决定了目标属性的值(value)是否可以被改变;如果原型对象的某个属性的writable为false,那么子对象将无法自定义这个属性
- value:设置值,如果不设置默认是undefined
- get:访问器函数
- set:访问器函数
一共是6个属性,但是只可能同时出现4个
数据属性:value+writable+configurable+enumerable
访问器属性:get+set+configurable+enumerable
Vue2是使用Object.defineProperty的get和set监听数据的,缺点是无法监听数据的变化,并且需要递归对象的属性才能监听,所以新增和删除属性也不能被监听
什么都不设置默认值是undefined,不可修改、不可枚举
const obj = {}
Object.defineProperty(obj,'age',{})
obj.age = 1;
console.log(obj.age)
console.log(Object.getOwnPropertyDescriptor(obj,'age'));
对象属性的类型
- 普通属性:普通的属性比如自己定义的字符串属性
- 不可枚举的属性:通过Object.defineProperty定义时标注enumerable为false;还有比如Object.prototype.toString等
- 原型属性,来自原型链;比如:Object.prototype.toString
- Symbol属性:标记唯一性
- 静态属性,不需要我们定义就能使用的属性
const symbolSay = Symbol.for("say1");
class Person {
// 静态属性
static flag = "人";
static getFlag() {
return Person.flag;
}
static [Symbol.for("symbolPro")](){
return "symbolPro"
}
constructor(name){
// 实例属性
this.name = name;
this[symbolSay] = "hello";
}
// 原型属性
getName() {
return this.name;
}
// 实例方法
getAge = () => {
return 15;
}
}
const p = new Person("张三");
console.log(p)
对象的可扩展
Object.preventExtensions
设置之后对象不可扩展,但是可以删除和修改值;使用Object.isExtensible()检查对象是否可扩展
const obj1 = {name:"jhon"};
const obj2 = {name:"candy"};
Object.preventExtensions(obj1);
obj1.age = 10;
delete obj1.name;
console.log(obj1) // 得到空对象 {}
console.log(Object.isExtensible(obj1)) // false
console.log(Object.isExtensible(obj2)) // true
对象的封闭
Object.seal;阻止添加新属性并且属性标记为不可配置,不可扩展也不可删除;使用Object.isSeal()判断对象是否被封闭
对象的冻结
Object.freeze():不可扩展、不可配置、不可修改;使用Object.isFrozen()判断对象是否被冻结;使用Object.defineProperty还会报错
对象的遍历
获取对象的全部静态属性
获取除了原型属性之外的属性,然后将其他属性排除
使用Reflect.ownKeys可以获取除原型属性之外的属性,包括普通属性、不可枚举属性和Symbol属性
Reflect.ownKeys = Object.getOwnPropertyNames + Object.getOwnPropertySymbols
let keys = Object.getOwnPropertyNames(_obj);
keys = keys.concat(Object.getOwnPropertySymbols(_obj));
这样得到的keys等同于Reflect.ownKeys(_obj)得到的keys
function getOwnPropertyStatics(_obj) {
// 需要排除的属性
const KNOWN_STATICS = {
name:true,
length:true,
prototype:true,
caller:true,
callee:true,
arguments:true,
arity:true
}
let res = [];
const keys = Reflect.ownKeys(_obj);
console.log(keys)
for(let i=0;i<keys.length;i++){
if(!KNOWN_STATICS[keys[i]]){
res.push(keys[i])
}
}
return res;
}
console.log(getOwnPropertyStatics(Person))
获取对象原型上的属性
for in 能拿到原型上的属性但是拿不到symbol属性;所以只能遍历原型使用Reflect.ownKeys()获取属性
class Grand {
gName = "Grand";
gGetName() {
return this.gName;
}
}
Grand.prototype[Symbol.for("gAge")] = "G-12";
class Parent extends Grand {
pName = "123";
pGetName(){
return this.pName;
}
}
Parent.prototype[Symbol.for("pAge")] = "p-12";
class Child extends Parent {
cName = "123";
cGetName(){
return this.cName;
}
}
const c = new Child();
function logAllProperties(instance) {
if(instance === null) return [];
let result = [];
let proto = instance.__proto__;
while (proto) {
result.push(...Reflect.ownKeys(proto));
proto = proto.__proto__;
}
return result;
}
console.log(logAllProperties(c))// 这里没有排除重复属性
获取不可枚举属性
先获取所有属性,然后判断是否可枚举
const obj = {
name:"yang",
age:15
}
Object.defineProperty(obj, "val",{
value:25,
enumerable:false
})
Object.defineProperty(obj, "price",{
value:35,
enumerable:false
})
console.log(Reflect.ownKeys(obj)) // ['name', 'age', 'val', 'price']
function getNoEnumerable(_obj) {
const keys = Reflect.ownKeys(_obj);
return keys.filter((key)=>{
// Object.prototype.propertyIsEnumerable.call(_obj,key),都是判断是否可枚举
return !Object.getOwnPropertyDescriptor(_obj,key).enumerable
})
}
console.log(getNoEnumerable(obj)) // ['val', 'price']
访问对象的原型
prototype:获取对象原型
原型会形成原型链,原型链上查找比较耗时,访问不存在的属性时会访问整个原型链,prototype是一个对象
class Person {
// 私有属性
#canTalk = true;
// 静态属性
static isAnimal = true;
constructor(name,age) {
// 实例属性
this.name = name;
this.age = age;
}
// 原型属性
sayName() {
console.log(this.name);
}
}
用ES5实现
- 对于私有属性ES5使用闭包
- 静态属性直接属性
- 实例属性使用this挂载
- 原型属性使用Object.defineProperty挂载到构造函数的原型上
Boolean.prototype上有一个属性叫BooleanData而Object.prototype.toString方法返回Boolean的条件是有BooleanData属性
所以Object.prototype.toString(Boolean.prototype) // [Object Boolean]
proto
构造函数的原型,除null外的对象都有__proto__属性
- Function、class的实例既有prototype又有__proto__属性
- __proto__是原型上的属性,不是自身属性
- 一个普通对象,祖上两代为null;new出来的对象不是普通对象
- 一个普通函数,祖上三代必为null
function a() {}
console.log(a.__proto__.__proto__.__proto__) // null
function Person() {}
const p = new Person();
console.log(p.__proto__.__proto__.__proto__) // null
instanceof
检测构造函数的prototype属性是否出现在某个实例对象的原型上
手写
function myInstanceof(left,right) {
let proto = left.__proto__;
const prototype = right.prototype;
while (proto) {
if(proto === prototype){
return true;
}
proto = proto.__proto__;
}
return false;
}
Object instanceof Function 和 Function instanceof Object
我们先获取实例的所有原型
function getPrototypeArr(instance) {
const protoArr = [];
let proto = instance.__proto__;
while (proto) {
protoArr.push(Object.prototype.toString.call(proto));
proto = proto.__proto__;
}
return protoArr;
}
console.log(getPrototypeArr(Function))
// ['[object Function]', '[object Object]']
console.log(getPrototypeArr(Object))
// ['[object Function]', '[object Object]']
所以Function的原型上有Object;Object的原型上也有Function;所以上面的两个都是true
getPrototypeOf:返回指定对象的原型
Object.getPrototypeOf或者Reflect.getPrototypeOf是一样的
Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法;它不是一个构造函数,它里面包含了对象的所有操作
在 ES5 中,如果 obj 参数不是对象,则会抛出 TypeError 异常。在 ES2015 中,该参数将被强制转换为 Object。
console.log(Object.getPrototypeOf(‘123’)) // String
对于null和undefined是会报错的
setPrototypeOf:指定对象的原型
Object.setPrototyprOf或者Reflect.setPrototypeOf;原型链的尽头是null
const obj = {a:1};
console.log(obj.toString());
Reflect.setPrototypeOf(obj,null);
console.log(obj.toString());
isPrototypeOf
检查一个对象是否存在于另一个对象的原型链上;因为是出现在对象的原型链上,所以要保证另一个对象一定是原型
区别:Object.isPrototypeOf、Object.prototype.isPrototypeOf、Reflect.isPrototypeOf、Function.isPrototypeOf
console.log(Object.isPrototypeOf({})) // false;Object不是原型
console.log(Object.prototype.isPrototypeOf({})) // true Object.prototype是原型
console.log(Reflect.isPrototypeOf({})) // false
console.log(Function.isPrototypeOf({})) // false
和instanceof一样,去取了右操作数的原型
Object.create
创建纯净对象,使用现有对象来提供新创建的对象__proto__
接受两个参数第二个参数是描述,在ES3时需要判断描述是否是undefined然后抛出错误
Object.prototype.myCreate = function(p){
function F(){};
F.prototype = p;
return new F();
}