对象认知全提升,成为 JS 高手

时间:2022-10-13 19:53:39

1.对象属性

常规属性

  • 键为字符串的属性
  • 根据创建时的顺序排序
const obj = {};

obj.p1 = "p1";
obj.p6 = "p6";
obj.p2 = "p2";

for (const p in obj) {console.log("property:", p);
} 

执行结果:

对象认知全提升,成为 JS 高手

排序属性

  • 属性键值为数字或者数字字符串的属性
  • 按照索引值大小升序排序
const obj = {};

obj[1] = "p1";
obj[6] = "p6";
obj[2] = "p2";
//obj["1"] = "p1";
//obj["6"] = "p6";
//obj["2"] = "p2";

for (const p in obj) {console.log("property:", p);
} 

执行结果:

对象认知全提升,成为 JS 高手

同时存在先输出排序属性

const obj = {};

obj.p1 = "str1";
obj.p6 = "str6";
obj.p2 = "str2";

obj[1] = "num1";
obj[6] = "num6";
obj[2] = "num2";

for (let p in obj) {console.log("property:", obj[p]);
} 

执行结果:

对象认知全提升,成为 JS 高手

为什么要设计常规属性和排序属性

  • 使用两种线性结构保存(elements、properties),提升V8引擎属性的访问速度

2.属性来源

  • 静态属性,例如: Object.assign
  • 原型属性,例如: Object.prototype.toString
  • 实例属性,例如: function Person (name){ this.name = name }
// 1.函数作为构造实例
function Person(name, age) {this.name = name;this.age = age;this.getName = function () {return name;};
}

Person.prototype.getAge = function () {return this.age;
};

const person = new Person();

// 2.class 实例对象
class Person {constructor(name, age) {this.name = name;this.age = age;}getName = () => {return this.name;};getAge() {return this.age;}
}

const hasOwn = Object.hasOwnProperty;
const print = console.log;

const person = new Person();
print("getName:", hasOwn.call(person, "getName")); // 实例属性
print("getAge:", hasOwn.call(person, "getAge")); // 原型属性

// 3.Object.defineProperty
const obj = {};
Object.defineProperty(obj, "name", {value: "云牧",
});

console.log("name:", obj.name);// 云牧 

3.属性描述符

  • Object.definePropertyObject.defineProperties 设置属性信息
  • Object.getOwnPropertyDescriptorObject.getOwnPropertyDescriptors 获取属性描述信息* configurable:可配置(属性能不能被删除和重新通过 defineProperty 设置,但是当设置 writablevaluetruefalse 则是允许的)* enumerable:是否可枚举* value:值* writable:是否可被更改* set:访问器函数* get:访问器函数
  • 数据属性: value + writable + configurable + enumerable
  • 访问器属性:get + set + configurable + enumerable

默认 defineProperty 不传第三个描述符

const obj = {};

Object.defineProperty(obj, "name", {});

console.log(obj.name); // undefined 且不能被改写

console.log(Object.getOwnPropertyDescriptor(obj, "name")); 

执行结果如下:

对象认知全提升,成为 JS 高手

Object.defineProperty的缺点

  • 无法监听数组变化
  • 只能劫持对象的属性,因此我们需要对对象的每个属性进行遍历。如果属性也是对象,还得进行递归

4.对象限制

1.对象可扩展-Object.preventExtensions

  • Object.preventExtensions:对象变的不可扩展,也就是永远不能再添加新的属性
  • Object.isExtensible:判断一个对象是否是可扩展
const obj = { name: "张三" };

Object.preventExtensions(obj);

// 不可以添加新属性
obj.age = 2;

console.log("obj:", obj); // obj: { name: '张三' }
console.log(Object.isExtensible(obj)); // false 

2.对象的封闭-Object.seal

  • Object.seal:阻止添加新属性+属性标记为不可配置
  • Object.isSealed:检查一个对象是否被密封
//2. Object.seal
const object1 = {prop1: 11,
};

Object.seal(object1);

// 不可以 添加属性
object1.prop2 = 22;
console.log(object1.prop2); // undefined

// 不可以 删除属性
delete object1.prop1;
console.log(object1.prop1); // 11 

3.对象的冻结- Object.freeze

  • Object.freeze:不加新属性+不可配置+不能修改值
  • Object.isFrozen:检查一个对象是否被冻结
const obj = {prop1: 11,
};

Object.freeze(obj);

// 添加
obj.prop2 = "prop2";
// 修改值
obj.prop1 = 33;
// 删除
delete obj.prop1;

Object.defineProperty(obj, "prop1", {value: 10,
});

console.log(obj.prop1);
console.log(obj.prop2);
console.log(Object.isFrozen(obj)); 

4.总结

方法 新增属性 修改描述符 删除属性 更改属性值
Object.preventExtensions x
Object.seal x x(修改 writable 为 false 可以) x
Object.freeze x x(修改 writable 为 false 可以) x x

5.访问对象原型

1.prototype

  • prototype是一个对象
  • 原型会形成原型链,原型链上查找属性比较耗时,访问不存在的属性会访问整个原型链

2._proto_

  • 构造函数的原型
  • null以外的对象均有 _proto_ 属性
  • Functionclass的实例有 prototype 以及 _proto_ 属性
  • 普通函数,祖上第三代上必为null
// 普通函数
function a() {}
console.log(a.__proto__.__proto__.__proto__); // null

// 作为构造函数
function Person() {}
const person = new Person();
console.log(person.__proto__.__proto__.__proto__); // null

// 普通对象 两代
const obj = {};
console.log(obj.__proto__.__proto__); // null 

3.instanceof

  • 检测构造函数(右侧)的prototype属性是否出现在某个实例对象(左侧)的原型链上
  • Object instanceof FunctionFunction instanceof Object

手写instanceof

function instanceOf(instance, cclass) {let proto = instance.__proto__;let prototype = cclass.prototype;while (proto) {if (proto === prototype) return true;proto = proto.__proto__;}return false;
}

class Parent {}
class Child extends Parent {}
class CChild extends Child {}
class Luren {}
const cchild = new CChild();

console.log(instanceOf(cchild, Parent)); // true
console.log(instanceOf(cchild, Child)); // true
console.log(instanceOf(cchild, CChild)); // true
console.log(instanceOf(cchild, Object)); // true
console.log(instanceOf(cchild, Date)); // false
console.log(instanceOf(cchild, Luren)); // false 

4.getPrototypeOf

  • Object.getPrototypeof()Reflect.getPrototypeOf()* 返回对象的原型
  • 内部先toObject转换,注意nullundefined没有原型

5.setPrototypeOf

  • Object.setPrototypeof()Reflect.setPrototypeOf()* 指定对象的原型
  • 原型的尽头是null
const obj = { a: 1 };
console.log(obj.toString());

Object.setPrototypeOf(obj, null); // 设置原型为null
console.log(obj.toString()); // obj.toString is not a function 

6.isPrototypeOf

  • Object.isPrototypeof 、 Object.prototype.isPrototypeof 、 Reflect.isPrototypeOf 、 Function.isPrototypeOf* 一个对象是否存在于另一个对象的原型链上
const print = console.log;

print(Object.isPrototypeOf({})); // false
print(Object.prototype.isPrototypeOf({})); // true期望左操作数是一个原型
print(Reflect.isPrototypeOf({})); // false
print(Function.isPrototypeOf({})); // false 

7.Object.create

  • 使用现有的对象来提供新创建的对象的 __proto__
  • 使用 Object.create(null) 可以创建一个没有原型的纯净对象

6.对象属性的遍历

属性分类:

  • 普通属性
  • 原型属性
  • Symbol属性
  • 不可枚举的属性

遍历方法:

方法名 普通属性 不可枚举属性 Symbol属性 原型属性
for in x x
Obiect.keys x x x
Object.getOwnPropertyNames x x
Object.getOwnPropertySymbols x x
Reflect.ownKeys x

1.获取非原型属性

  • Reflect.ownKeys = Object.getOwnPropertyNames + Object.getOwnPropertySymbols
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] = "haha";}getName() {return this.name;}getAge = () => {return 15;};
}

function getOwnPropertyStatics(_obj) {const KNOWN_STATICS = {name: true,length: true,prototype: true,caller: true,callee: true,arguments: true,arity: true,};let result = [];let keys = Object.getOwnPropertyNames(_obj);keys = keys.concat(Object.getOwnPropertySymbols(_obj));// const keys = Reflect.ownKeys(_obj)for (let i = 0; i < keys.length; ++i) {const key = keys[i];if (!KNOWN_STATICS[key]) {result.push(key);}}return result;
}

const staticProps = getOwnPropertyStatics(Person);
console.log("非原型属性:", staticProps); // 非原型属性: [ 'getFlag', 'flag', Symbol(symbolPro) ] 

2.获取原型上所有属性

  • 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")] = "G-11";

class Child extends Parent {cName = "123";cGetName() {return this.cName;}
}

const child = new Child();

let result = [];
function logAllProperties(instance) {if (instance == null) return;let proto = instance.__proto__;while (proto) {result.push(...Reflect.ownKeys(proto));proto = proto.__proto__;}
}
logAllProperties(child);
console.log("result:", result); 

执行结果如下:

对象认知全提升,成为 JS 高手

3.获取所有不可枚举的属性

const symbolSalary = Symbol.for("ins_symbol_attr_salary");

function Person(age, name) {this.ins_in_attr_age = age;this.ins_in_attr_name = name;
}

const person = new Person(100, "程序员");

//Symbol 属性
person[symbolSalary] = 6000;
person["ins_no_enumerable_attr_sex"] = "男";

// sex 不可枚举
Object.defineProperty(person, "ins_no_enumerable_attr_sex", {enumerable: false,
});

Object.defineProperty(person, symbolSalary, {enumerable: false,value: 999,
});

//
function getNoEnumerable(_obj) {//获取原型对象const keys = Reflect.ownKeys(_obj);// const result = keys.filter(key=> {// return !Object.getOwnPropertyDescriptor(_obj, key).enumerable// })// return result;const result = keys.filter((key) => {return !Object.prototype.propertyIsEnumerable.call(_obj, key);});return result;
}

console.log(getNoEnumerable(person)); 

执行结果如下:

对象认知全提升,成为 JS 高手

7.对象隐式转换和注意事项

1.显示转换

  • 通过 JS 转换方法进行转换、
  • 比如 String 、 Number 、 parselnt/parseFloat 等

2.隐式转换

  • 编译器自动完成类型转换的方式就称为隐式转换,往往预期和传入不一致往往就会发生* 二元 + 运算符(类型不一样的相加)* 关系运算符 ><>=<===* 逻辑!if/while三目条件* 属性键的遍历for in等* 模板字符串

3.对象隐式转换规则

涉及到三个方法

  • Symbol.toPrimitive* Object.prototype.valueOf* Object.prototype.toString* 如果[Symbol.toPrimitive](hint)方法存在,优先调用,无视valueOftoSting方法* 否则,如果期望是"string" ——先调用obj.toString()如果返回不是原始值,继续调用obj.valueOf()* 否则,如果期望是"number"或"default" 先调用 obj.valueOf() 如果返回不是原始值,继续调用obj.toString()如果未定义[Symbol.toPrimitive](hint),期望string,此时toString()valueOf()都没有返回原始值会抛出异常
const obj = {value: 10,valueOf() {return this;},toString() {return this;},
};

console.log(10 + obj); // 报错 

4.Symbol.toPrimitive(hint)

  • hint - “string”
  • hint - “number”
  • hint - “default”

hint - “string”

  • window.alert(obj)
  • 模板字符串`${obj}
  • test[obj]=123
const obj = {[Symbol.toPrimitive](hint) {if (hint == "number") {return 10;}if (hint == "string") {return "hello";}return true;},
};
// alert, 浏览器
// window.alert(obj);
// ${}
console.log(`${obj}`);
// 属性键
obj[obj] = 123;
console.log(Object.keys(obj)); 

执行结果:

对象认知全提升,成为 JS 高手

hint - “number”

  • 一元+,位移
  • -*/ 等关系运算符
  • Math.powStringprototype.slice 等很多内部方法
const obj = {[Symbol.toPrimitive](hint) {if (hint == "number") {return 10;}if (hint == "string") {return "hello";}return true;},
};

// 一元+
console.log("一元+:", +obj);

// 位移运算符
console.log("位移运算符", obj >> 0);

// 除减算法, 没有 + 法,之前已经单独说过转换规则
console.log("减法:", 5 - obj);
console.log("乘法:", 5 * obj);
console.log("除法:", 5 / obj);

// 逻辑 大于,小于,大于等于, 没有等于, 有自己的一套规则
console.log("大于:", 5 > obj);
console.log("大于等于:", 5 >= obj);

// 其他期望是整数的方法
console.log("Math.pow:", Math.pow(2, obj)); 

执行结果如下:

对象认知全提升,成为 JS 高手

hint - “default”

  • 二元+
  • == 、!=
const obj = {[Symbol.toPrimitive](hint) {if (hint == "number") {return 10;}if (hint == "string") {return "hello";}return true;},
};

console.log("相加:", 5 + obj); // 相加: 6
console.log("等等与:", 5 == obj); // 等等与: false
console.log("不等于:", 5 != obj); // 不等于: true 

5.ValueOf 和 toString

来几个小练习大家自己想想

const user = {name: "John",age: 10,toString() {return this.name;},valueOf() {return this.age;},
};

console.log("user:", +user); // user: 10
console.log("user:", `${user}`); // user: John 
const user = {name: "John",age: 10,toString() {return this.name;},valueOf() {return this;},
};

console.log("user:", +user); // NaN
// 相当于
console.log(+"John"); // NaN 
const user = {name: "John",age: 10,toString() {return this;},valueOf() {return this.age;},
};

Object.prototype.toString = undefined;

console.log("user:", `${user}`); // user: 10 
const obj = {value: 10,toString: function () {return this.value + 10;},valueOf: function () {return this.value;},
};

obj[obj] = obj.value;

console.log("keys:", Object.keys(obj)); // keys: [ '20', 'value', 'toString', 'valueOf' ]
console.log("${obj}:", `${obj}`); // ${obj}: 20
console.log("obj + 1:", obj + 1); // obj + 1: 11
console.log('obj + "":', obj + ""); // obj + "": 10 

特殊Date

  • hint是default ,是优先调用toString,然后调用valueOf
const date = new Date();

console.log("date toString:", date.toString());
console.log("date valueOf:", date.valueOf());

console.log(`date str:`, `${date}`);
console.log(`date number:`, +date);

console.log(`date +:`, date + 1); 

执行结果如下:

对象认知全提升,成为 JS 高手

8.JSON和toJSON

  • 严格意义上JSON对象是不合理,JSON是文本协议
  • 全局作用域下JSON,名为JSON,是Object对象
  • JSON是一种轻量级的、基于文本的、与语言无关的语法,用于定义数据交换格式
  • 它来源于ECMAScript编程语言,但是独立于编程语言

JSON特征

  • JSON就是一串字符串,使用特定的符号标注* {}双括号表示对象* []中括号表示数组* ""双引号内是属性键或值​### 属性键

  • 只能是字符串

  • 必须双引号包裹

JSON值

  • object
  • array
  • number(只能十进制)
  • string
  • true
  • false
  • null

合格JSON

`["你", "我", "她"]`

`{ "name": "云牧", "age": 18 }`

`{ "IDS": ["123", "456"] }`

`{ "name": null }`

`{}` 

`[]` 

不合格JSON

`
{"name":"云牧",[Symbol.for("sex")]: 1 
}`

`
{ name: "云牧", 'age': 32} `

`
{"name": "云牧","age": undefined 
}`

`[-10, 0xDDFF]`  
`
{ "name": "云牧","created": new Date(),"price": 18"getPrice": function() { return this.price;}
}`

`
{ "name":"云牧", "age": 32, 
}
` 

JSON.stringify()

  • 语法:JSON.stringify(value[, replacer [, space]])
  • 第二个参数replacer:过滤属性或者处理值* 如果该参数是一个数组:则只有包含在这个数组中的属性名才会被序列化到最终的JSON字符串中* 如果该参数是一个函数︰则在序列化过程中,被序列化的值的每个属性都会经过该函数的转换和处理* 如果该参数为null或者未提供:,则对象所有的属性都会被序列化
  • 第三个参数space:美化输出格式
const person1 = {name: "云牧",age: 18,birth: "2002-01-01",
};

//replacer 数组
console.log(JSON.stringify(person1, ["name", "age"])); // {"name":"云牧","age":18}

const person2 = {name: "云牧",age: 18,birth: "2002-01-01",
};

//replacer 方法
const jsonString = JSON.stringify(person2, function (key, value) {if (typeof value === "string") {return undefined;}return value;
});

console.log(jsonString); // {"age":18}

// space 美化格式
const person3 = {name: "云牧",age: 18,birth: "2002-01-01",
};
const a = JSON.stringify(person3);
console.log(a); // {"name":"云牧","age":18,"birth":"2002-01-01"}

const person4 = {name: "云牧",age: 18,birth: "2002-01-01",
};
const c = JSON.stringify(person4, null, "\t");
console.log(c);
// {
// 	"name": "云牧",
// 	"age": 18,
// 	"birth": "2002-01-01"
// } 

序列化undefined、任意的函数、symbol

  • 作为对象属性值,自动忽略
  • 作为数组,序列化返回null
  • 单独序列化时,返回undefined

其他规则

  • Date返回 ISO 字符串
  • 循环引用报错
  • NaNInfinitynull都会作为null
  • Biglnt报错
  • MapSetWeakMap等对象,仅序列化可枚举属性
// 自动忽略
const data1 = {a: "test1",b: undefined,c: Symbol("test2"),fn: function () {return true;},
};
console.log(JSON.stringify(data1)); // {"a":"test1"}

//数组返回null
const data2 = ["test1",undefined,function aa() {return true;},Symbol("test2"),
];
console.log(JSON.stringify(data2)); // ["test1",null,null,null]

//返回undefined
const a1 = JSON.stringify(function a() {console.log("test1");
});
console.log("a1:", a1); // a1: undefined
const a2 = JSON.stringify(undefined);
console.log("a2:", a2); // a2: undefined
const a3 = JSON.stringify(Symbol("test2"));
console.log("a3:", a3); // a3: undefined

// Date
console.log(JSON.stringify({ now: new Date() })); // {"now":"2022-09-12T00:17:54.812Z"}

// NaN 和 Infinity 以及null
console.log(JSON.stringify(NaN)); // null
console.log(JSON.stringify(Infinity)); // null
console.log(JSON.stringify(null)); // null

//转换为对应的原始值。
console.log(JSON.stringify([new Number(2), new String("test"), new Boolean(false)])); // [2,"test",false]

//仅序列化可枚举属性
const a = JSON.stringify(Object.create(null, {test1: { value: "test1", enumerable: false },test2: { value: "test2", enumerable: true },})
);
console.log(a); // {"test2":"test2"}

// BigInt 报错
// const c = {
// test: 1n,
// };
// console.log(JSON.stringify(c)); 

JSON.parse()

  • 注意:第二个参数函数reviver ( k, v )* k代表属性键,v代表属性值,如果返回undefined则会从当前的属性删除
const jsonStr = `
	{ 	"name": "帅哥", 	"age":18, "isFans": true,"IDCard": "xxxxxxxxxxxxxxxxxx" }
`;
// 保密身份证
const obj = JSON.parse(jsonStr, function (key, value) {if (key == "IDCard") {return undefined;} else {return value;}
});

console.log(obj); // { name: '帅哥', age: 18, isFans: true } 

注意:遍历顺序

const jsonStr = `{"name": "牙膏","count": 10, "orderDetail": {"createTime": 1632996519781,"orderId": 8632996519781,"more": {"desc": "描述"}}
}`;

JSON.parse(jsonStr, function (k, v) {console.log("key:", k);return v;
}); 

执行结果如下:

对象认知全提升,成为 JS 高手

注意:this

const jsonStr = `{"name": "云牧","orderDetail": {"createTime": 1632996519781}
}`;

JSON.parse(jsonStr, function (k, v) {console.log("key:", k, ",this:", this);return v;
}); 

执行结果如下:

对象认知全提升,成为 JS 高手

toJSON

  • 对象拥有toJSON方法,toJSON会覆盖对象默认的序列化行为
const product = {name: "牙膏",orderDetail: {createTime: 1632996519781,},toJSON() {return {name: "云牧",};},
};

console.log(JSON.stringify(product)); // '{"name":"云牧"}' 

使用场景

  • 请求接口发送数据,接收数据
  • 本地存储
  • 深克隆对象

9.学习自检

题目一

const obj = {},objA = { propertyA: "A" },objB = { propertyB: "B" };

obj[objA] = "objectA";
obj[objB] = "ObjectB";

for (let [p, v] of Object.entries(obj)) {console.log("p:", p, ", v:", v);
} 

执行结果:

对象认知全提升,成为 JS 高手
  • Object.entires :迭代器,能获取键值对
  • 对象键的特性∶本质上是字符串,如果是数字,转换字符串
  • 隐式转换︰对象的隐式转换,Symbol.toPrimitive,valueOf,toString()
const obj = {},objA = {propertyA: "A",toString() {return "objA";},},objB = {propertyB: "B",valueOf() {return "objB";},};

obj[objA] = "objectA";
obj[objB] = "ObjectB";

for (let [p, v] of Object.entries(obj)) {console.log("p:", p, ", v:", v); // 优先调用toString // p: objA , v: objectA // p: [object Object] , v: ObjectB
} 

题目二

const person = {name: "二哈",
};
const person2 = Object.create(person);
delete person2.name;

console.log(person2.name); 

执行结果:

对象认知全提升,成为 JS 高手

题目三

const val = (+{} + [])[+[]];
console.log(val);

/*

(+{} + [])[+[]]
// +{}=> NaN
(NaN + [])[+[]]
// [] 隐式转换 ''
(NaN + '')[+[]]
// NaN + '' => 'NaN'
('NaN')[+[]]
// +[] => 0
('NaN')[0]
// 'N'

*/ 

题目四

const proto = {name: "原型",arr: [1, 2],
};
const person = Object.create(proto);
person.name = "实例";
person.arr.push(3);

console.log(person.name);
console.log(proto.name);

console.log(person.arr);
console.log(proto.arr); 

执行结果:

对象认知全提升,成为 JS 高手

题目五

const toString = Object.prototype.toString;
function getObjectType(obj) {return toString.call(obj).slice(8, -1);
}
const obj = String.prototype;
console.log(typeof obj);
console.log(getObjectType(obj)); 

执行结果:

对象认知全提升,成为 JS 高手

题目六

let a = { n: 1 };
a.x = a = { n: 2 };

// 求a.x
console.log(a.x); 

执行结果:

对象认知全提升,成为 JS 高手

题目七

const proto = {name: "p_parent",type: "p_object",[Symbol.for("p_address")]: "地球",
};

const ins = Object.create(proto);
Object.defineProperty(ins, "age", {value: 18,
});
ins.sex = 1;
ins[Symbol.for("say")] = function () {console.log("say");
};

const inKeys = [];
for (let p in ins) {inKeys.push(p);
}

console.log(inKeys);
console.log(Reflect.ownKeys(ins)); 

执行结果:

对象认知全提升,成为 JS 高手

最后

整理了一套《前端大厂面试宝典》,包含了HTML、CSS、JavaScript、HTTP、TCP协议、浏览器、VUE、React、数据结构和算法,一共201道面试题,并对每个问题作出了回答和解析。
对象认知全提升,成为 JS 高手
有需要的小伙伴,可以点击文末卡片领取这份文档,无偿分享

部分文档展示:
对象认知全提升,成为 JS 高手
对象认知全提升,成为 JS 高手
对象认知全提升,成为 JS 高手
对象认知全提升,成为 JS 高手

文章篇幅有限,后面的内容就不一一展示了

有需要的小伙伴,可以点下方卡片免费领取