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
则会从当前的属性删除
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));
执行结果:
最后
整理了一套《前端大厂面试宝典》,包含了HTML、CSS、JavaScript、HTTP、TCP协议、浏览器、VUE、React、数据结构和算法,一共201道面试题,并对每个问题作出了回答和解析。
有需要的小伙伴,可以点击文末卡片领取这份文档,无偿分享
部分文档展示:
文章篇幅有限,后面的内容就不一一展示了
有需要的小伙伴,可以点下方卡片免费领取