对象认知全提升,成为 JS 高手
1.对象属性
常规属性
- 键为字符串的属性
- 根据创建时的顺序排序
const obj = {};
obj.p1 = "p1";
obj.p6 = "p6";
obj.p2 = "p2";
for (const p in obj) {
console.log("property:", p);
}
执行结果:
排序属性
- 属性键值为数字或者数字字符串的属性
- 按照索引值大小升序排序
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);
}
执行结果:
同时存在先输出排序属性
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]);
}
执行结果:
为什么要设计常规属性和排序属性
- 使用两种线性结构保存(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.defineProperty
、Object.defineProperties
设置属性信息Object.getOwnPropertyDescriptor
、Object.getOwnPropertyDescriptors
获取属性描述信息configurable
:可配置(属性能不能被删除和重新通过defineProperty
设置,但是当设置writable
和value
从true
到false
则是允许的)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"));
执行结果如下:
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_
属性Function
、class
的实例有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 Function
、Function 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
转换,注意null
和undefined
没有原型
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);
执行结果如下:
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));
执行结果如下:
7.对象隐式转换和注意事项
1.显示转换
- 通过 JS 转换方法进行转换、
- 比如 String 、 Number 、 parselnt/parseFloat 等
2.隐式转换
- 编译器自动完成类型转换的方式就称为隐式转换,往往预期和传入不一致往往就会发生
- 二元 + 运算符(类型不一样的相加)
- 关系运算符
>
、<
、>=
、<=
、==
逻辑!
、if/while
,三目条件
属性键的遍历
、for in
等- 模板字符串
3.对象隐式转换规则
涉及到三个方法
-
Symbol.toPrimitive
-
Object.prototype.valueOf
-
Object.prototype.toString
-
如果
[Symbol.toPrimitive](hint)
方法存在,优先调用,无视valueOf
和toSting
方法 -
否则,如果期望是"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));
执行结果:
hint - "number"
- 一元+,位移
-
、*
、/
等关系运算符Math.pow
、String
、prototype.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));
执行结果如下:
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);
执行结果如下:
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
字符串- 循环引用报错
NaN
、Infinity
、null
都会作为null
Biglnt
报错Map
、Set
、WeakMap
等对象,仅序列化可枚举属性
// 自动忽略
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
则会从当前的属性删除
- k代表属性键,v代表属性值,如果返回
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;
});
执行结果如下:
注意:this
const jsonStr = `{
"name": "云牧",
"orderDetail": {
"createTime": 1632996519781
}
}`;
JSON.parse(jsonStr, function (k, v) {
console.log("key:", k, ",this:", this);
return v;
});
执行结果如下:
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);
}
执行结果:
- 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);
执行结果:
题目三
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);
执行结果:
题目五
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));
执行结果:
题目六
let a = { n: 1 };
a.x = a = { n: 2 };
// 求a.x
console.log(a.x);
执行结果:
题目七
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));
执行结果:
转载自:https://juejin.cn/post/7142494266927874078