对象
合并
对象合并也称为mixin,js提供了方法Object.assign.
Object.assign执行的是浅复制,在有多个源的情况下,如果key有相同的,则以最后的一次为准
还有在复制过程中,中间出现异常,不会回退,也就是会出现只复制部分的问题
相等判定
Object.is可以用于之前对于使用===不能正确处理的情况
console.log(Object.is(+0, -0)) //输出为false
console.log(Object.is(+0, 0))//输出为true
console.log(Object.is(-0, 0))//输出为false
console.log(Object.is(NaN, NaN))//输出为true
增强的对象语法
- 属性简写
在属性名和变量名一致时,可以省略属性名
let name = "Matt"
let person = {
name
};
console.log(person)//输出为{ name: 'Matt' }
- 可计算属性
在通过对象字面量方式创建对象时可以动态属性赋值
const nameKey = "name"
const ageKey = "age"
const jobKey = "job"
let person = {
[nameKey]: "Matt",
[ageKey]: 27,
[jobKey]: "Software engineer"
}
console.log(person)//输出{ name: 'Matt', age: 27, job: 'Software engineer' }
可计算属性支持表达式运算
const nameKey = "name"
const ageKey = "age"
const jobKey = "job"
let uniqueToken = 0
function getUniqueKey(key)
{
return `${key}_${uniqueToken++}`;
}
let person = {
[getUniqueKey(nameKey)]: "Matt",
[getUniqueKey(ageKey)]: 27,
[getUniqueKey(jobKey)]: "Software engineer"
}
console.log(person)//输出为{ name_0: 'Matt', age_1: 27, job_2: 'Software engineer' }
- 方法名简写
与属性简写类似
let person = {
sayName(name) {
console.log(`My name is ${name}`);
}
}
person.sayName('Matt')
对象解构
就是使用与对象相匹配的结构来实现对对象赋值
let person = {
name:"Matt",
age: 27
};
let {name: personName, age: personAge} = person
console.log(personName, personAge)
上面代码表示将person中的name赋值给personName,age赋值给personAge
可以使用属性简写语法
let person = {
name:"Matt",
age: 27
};
let {name, age} = person
console.log(name, age)
在解析过程中,如果原对象的属性不存在,则赋值为undefined
let person = {
name:"Matt",
age: 27
};
let {name, job} = person
console.log(name, job) //输出为Matt undefined
对于上面因为原对象属性不存在导致赋值为undefined时,可以为其指定默认值
let person = {
name:"Matt",
age: 27
};
let {name, job = "Software engineer"} = person
console.log(name, job)//输出为Matt Software engineer
解构内部是通过函数ToObject将源数据结构转为对象,那么null和undefined解析进会抛出异常
let {_} = undefined
会抛出异常
let {_} = undefined
^
TypeError: Cannot destructure property '_' of 'undefined' as it is undefined.
at Object.<anonymous> (/Users/wuli06/test.js:2:6)
at Module._compile (node:internal/modules/cjs/loader:1275:14)
at Module._extensions..js (node:internal/modules/cjs/loader:1329:10)
at Module.load (node:internal/modules/cjs/loader:1133:32)
at Module._load (node:internal/modules/cjs/loader:972:12)
at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:83:12)
at node:internal/main/run_main_module:23:47
Node.js v19.9.0
let {_} = null
let {_} = null
^
TypeError: Cannot destructure property '_' of 'null' as it is null.
at Object.<anonymous> (/Users/wuli06/test.js:2:6)
at Module._compile (node:internal/modules/cjs/loader:1275:14)
at Module._extensions..js (node:internal/modules/cjs/loader:1329:10)
at Module.load (node:internal/modules/cjs/loader:1133:32)
at Module._load (node:internal/modules/cjs/loader:972:12)
at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:83:12)
at node:internal/main/run_main_module:23:47
Node.js v19.9.0
解构赋值并没有要求在解构时声明变量,也可以先声明, 再解构赋值,此时要求解析赋值用括号括起来
let personName, personAge
let person = {
name:"Matt",
age: 27
};
({name:personName, age:personAge} = person)
console.log(personName, personAge)
嵌套解构要求外层属性存在,否则会抛出异常,源和目的都需要满足
let person = {
job: {
title: "Software engineer"
}
};
let personCopy = {};
({foo: {
bar: personCopy.bar
}
} = person);
抛出异常
bar: personCopy.bar
^
TypeError: Cannot read properties of undefined (reading 'bar')
at Object.<anonymous> (/Users/wuli06/test.js:12:10)
at Module._compile (node:internal/modules/cjs/loader:1275:14)
at Module._extensions..js (node:internal/modules/cjs/loader:1329:10)
at Module.load (node:internal/modules/cjs/loader:1133:32)
at Module._load (node:internal/modules/cjs/loader:972:12)
at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:83:12)
at node:internal/main/run_main_module:23:47
let person = {
job: {
title: "Software engineer"
}
};
let personCopy = {};
({job: {
title: personCopy.job.bar
}
} = person);
抛出异常
title: personCopy.job.bar
^
TypeError: Cannot set properties of undefined (setting 'bar')
at Object.<anonymous> (/Users/wuli06/test.js:12:23)
at Module._compile (node:internal/modules/cjs/loader:1275:14)
at Module._extensions..js (node:internal/modules/cjs/loader:1329:10)
at Module.load (node:internal/modules/cjs/loader:1133:32)
at Module._load (node:internal/modules/cjs/loader:972:12)
at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:83:12)
at node:internal/main/run_main_module:23:47
对象创建方式
工厂模式
通过函数来创建对象,函数有返回值,填充属性值
function createPerson(name, age, job)
{
let o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function() {
console.log(this.name);
}
return o;
}
let person1 = createPerson("Nicholas", 29, "Software engineer");
let person2 = createPerson("Greg", 27, "Doctor");
person1.sayName();
person2.sayName();
构造函数模式
以函数形式为对象设置属性和方法,创建是通过new
function Person(name, age, job)
{
this.name = name;
this.age = age;
this.job = job;
this.sayName = function() {
console.log(this.name);
}
}
let person1 = new Person("Nicholas", 29, "Software engineer");
let person2 = new Person("Greg", 27, "Doctor");
person1.sayName();//输出为Nicholas
person2.sayName();//输出为Greg
在使用new创建对象时,会将实例的[[prototype]]属性设置为构造函数的prototype属性,而原型对象中会有属性constructor指向构造函数
function Person(name, age, job)
{
this.name = name;
this.age = age;
this.job = job;
this.sayName = function() {
console.log(this.name);
}
}
let person1 = new Person("Nicholas", 29, "Software engineer");
let person2 = new Person("Greg", 27, "Doctor");
console.log(person1.constructor == Person);//输出为true,因为原型链关系,会取原型对象的constructor属性
console.log(person2.constructor == Person);//输出为true
一般使用instanceof来确定实例所属对象类型
function Person(name, age, job)
{
this.name = name;
this.age = age;
this.job = job;
this.sayName = function() {
console.log(this.name);
}
}
let person1 = new Person("Nicholas", 29, "Software engineer");
let person2 = new Person("Greg", 27, "Doctor");
console.log(person1 instanceof Person);//输出为true
console.log(person2 instanceof Person);//输出为true
相比工厂模式,构造函数模式可以标识实例所属对象类型
在构造函数模式中,除了使用函数声明外,也可以使用函数表达式
let Person = function(name, age, job)
{
this.name = name;
this.age = age;
this.job = job;
this.sayName = function() {
console.log(this.name);
}
}
构造函数模式问题是在定义方法时对于每个实例都会创建一个函数对象
function Person(name, age, job)
{
this.name = name;
this.age = age;
this.job = job;
this.sayName = function() {
console.log(this.name);
}
}
let person1 = new Person("Nicholas", 29, "Software engineer");
let person2 = new Person("Greg", 27, "Doctor");
console.log(person1.sayName == person2.sayName);//输出为false,说明是两个不同的对象
原型模式
原型上的属性和方法被所有实例共享
function Person() {}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software engineer";
Person.prototype.sayName = function() {
console.log(this.name);
};
let person1 = new Person();
person1.sayName();//输出为Nicholas
let person2 = new Person();
person2.sayName();//输出为Nicholas
console.log(person1.sayName == person2.sayName);//输出为true
实例,原型和构造函数关系
与原型相关的方法
isPrototypeOf用于原型
Object.getPrototypeOf
function Person() {}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software engineer";
Person.prototype.sayName = function() {
console.log(this.name);
};
let person1 = new Person();
let person2 = new Person();
console.log(Person.prototype.isPrototypeOf(person1));//输出true
console.log(Person.prototype.isPrototypeOf(person2));//输出true
console.log(Object.getPrototypeOf(person1) == Person.prototype);//输出true
console.log(Object.getPrototypeOf(person2) == Person.prototype);//输出true
使用hasOwnProperty()来判断属性是在实例还是原型上
function Person() {}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software engineer";
Person.prototype.sayName = function() {
console.log(this.name);
};
let person1 = new Person();
let person2 = new Person();
console.log(person1.hasOwnProperty("name"));//输出false
person1.name = "Greg";
console.log(person1.hasOwnProperty("name"));//输出true
delete person1.name;
console.log(person1.hasOwnProperty("name"));//输出false
使用in操作符判断属性是否存在时,无论是存在于实例还是原型上,都会返回 true
如果需要判断属性是在原型上,可以使用如下函数
function hasPrototypeProperty(object, name)
{
return !object.hasOwnProperty(name) && name in object;
}
对象中的属性遍历可以使用for in 或者Object.keys来遍历对象中的可枚举属性
Object.keys应用于实例时,不会返回原型上的属性
如果需要获取不可枚举的属性,可以使用Object.getOwnPropertyNames方法
获取对象中的符号键使用Object.getOwnPropertySymbols
function Person() {}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software engineer";
Person.prototype.sayName = function() {
console.log(this.name);
};
let keys = Object.keys(Person.prototype);
console.log(keys);//输出为[ 'name', 'age', 'job', 'sayName' ]
for (let key in Person.prototype) {
console.log(key);
}
/*输出为
name
age
job
sayName
*/
let p1 = new Person();
keys = Object.keys(p1);
console.log(keys);//输出为[]
let keys = Object.getOwnPropertyNames(Person.prototype);
console.log(keys);//输出为[ 'constructor', 'name', 'age', 'job', 'sayName' ]
let k1 = Symbol('k1');
let k2 = Symbol('k2');
let o = {
[k1]: 'k1',
[k2]: 'k2',
"ab":1
};
console.log(Object.getOwnPropertySymbols(o))//输出为[ Symbol(k1), Symbol(k2) ]
继承
原型链
默认情况下构造函数的原型指向原型对象。而通过原型链方式实现继承则是将父类的实例赋值给构造函数的原型
function SuperType() {
this.property = true;
}
SuperType.prototype.getSuperValue = function() {
return this.property;
}
function SubType() {
this.subproperty = false;
}
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function() {
return this.subproperty;
}
let instance = new SubType();
console.log(instance.getSuperValue());
盗用构造函数
与c++,java中的经典继承比较类似,子类构造函数中调用父类构造函数
function SuperType() {
this.colors = ["red", "blud", "green"];
}
function SubType() {
SuperType.call(this);
}
let instance1 = new SubType();
instance1.colors.push("black");
console.log(instance1.colors);
let instance2 = new SubType();
console.log(instance2.colors);
组合继承
综合了原型链和盗用构造函数
function SuperType(name) {
this.name = name
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function() {
console.log(this.name);
};
function SubType(name ,age) {
SuperType.call(this, name);
this.age = age;
}
SubType.prototype = new SuperType();
SubType.prototype.sayAge = function() {
console.log(this.age);
}
let instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
console.log(instance1.colors);
instance1.sayName();
instance1.sayAge();
let instance2 = new SubType("Greg", 27);
console.log(instance2.colors);
instance2.sayName();
instance2.sayAge();
原型式继承
创建临时构造函数,将对象赋值给临时构造函数的原型,然后创建临时对象的实例,es中的 Object.create就是原型式继承
function object(o)
{
function F() {}
F.prototype = o;
return new F();
}
原型式继承适用于不需要创建构造函数,便是希望在对象间共享的情况
寄生式继承
通过基准对象创建新对象,然后在新对象上做些增强
function object(o)
{
function F() {}
F.prototype = o;
return new F();
}
function createAnother(original)
{
let clone = object(original);
clone.sayHi = function() {
console.log("hi");
};
return clone;
}
let person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};
let anotherPerson = createAnother(person);
anotherPerson.sayHi();
寄生式继承与原型式继承一样,只关注对象,不关注类型和构造函数。通过寄生式继承给对象添加函数,函数不具有通用性
寄生式组合继承
组合继承是调用两次构造函数,导致属性重复,在实例和原型上都会存在
核心逻辑为
function inheritPrototype(subType, superType)
{
let prototype = Object.create(superType.prototype);
prototype.constructor = subType;
subType.prototype = prototype;
}