跳到主要内容

对象

基础

创建 object
  • new 操作符;
  • object literal notation (推荐使用);
  • Object.create()
let person = new Object();
let person = {};
const person2 = Object.create(person);
定义属性
let person = new Object();
person.name = "Nicholas";
people["name"] = "Nicholas";
let person = {
name: "Nicholas";
};
object 中的属性名转换
  • 数字转化为字符串;
  • 字符串和标识符转换成标识符;
let people = new Object();
people[5] = true;
console.log(people); // { '5': true }

people.name = "kxh";
console.log(people); // { name: 'kxh' }

people["name"] = "kxh";
console.log(people); // { name: 'kxh' }

对象进阶

对象属性

  • 表示属性值行为,必须通过 defineProperty() 自定义;
  • 属性行为;
    • configurable:表示属性可被删除/重新定义,默认为 true;
    • enumerable:表示属性可枚举,应用于 for-in 循环,默认为 true;
    • writable:表示属性值可被改变,默认为 true;
    • value:表示属性值,默认为 undefined;
    • get:当属性被读时调用的函数,默认值为 undefined;
    • set:当属性被写时调用的函数,默认值为 undefined;
      • 只定义 get,表明只读;
      • 只定义 set,表示只写;

对象语法增强

属性值简写
// 传统
let name = "Matt";
let person = {
name: name,
};
// 属性值简写
let name = "Matt";
let person = {
name,
};
可计算属性
// 传统
const nameKey = "name";
let person = {};
person[nameKey] = "Matt"; // { name: 'Matt' }
// 可计算属性
const jobKey = "job";
let person = {
[nameKey]: "Matt",
}; // { name: 'Matt' }
console.log(person); // { name: 'Matt' }
简写方法名
// 传统
let person = {
sayName: function (name) {
console.log("My name is ${name}");
},
};
// 简写方法名, 广泛应用于 class
let person = {
sayName(name) {
console.log("My name is ${name}");
},
};

Object 解构

Object 解构
// 未赋值的属性为 undefined
let person = {
name: "Matt",
age: 27,
};
let { name, job } = person;
console.log(name); // Matt
console.log(job); // undefined

// 函数参数使用解构
let person = {
name: "Matt",
age: 27,
};
function printPerson(foo, { name: personName, age: personAge }, bar) {
console.log(arguments);
console.log(personName, personAge);
}
printPerson("1st", person, "2nd");
// ['1st', { name: 'Matt', age: 27 }, '2nd']
// 'Matt', 27

设计模式

工厂模式

// 无法自定义返回对象类型
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");

构造函数模型

创建构造函数
  • 只能使用普通函数形式定义;
  • 创建一个新对象;
    • object 的 [[prototype]] 赋值 constructor function 的 prototype 属性;
    • constructor function 的 this 指向 object;
    • 执行 constructor function 内部代码;
    • 返回 object;
  • 不同 Person 实例的 constructor 指向 Person;
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
console.log(person2.constructor === Person); // true
// Person 实例可被表示为自定义类型
console.log(person1 instanceof Person); // true
构造函数和普通函数的 this 指向
  • 对象函数调用,this 指向 object;
  • 普通函数调用时不使用 new(),this 默认指向 global;
let person = new Person("Nicholas", 29, "Software Engineer");
person.sayName(); // "Nicholas"
Person("Greg", 27, "Doctor"); // adds to window
window.sayName(); // "Greg"

原型模式

prototype 属性
  • prototype 属性包含特定引用类型实例共享的属性和方法;
  • prototype 属性的 enumerable 定义为 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
prototype 属性机制
  • 每当创建一个 function,自动定义一个 prototype 属性,指向原型对象;
    • 原型对象为一个 object;
      • 原型对象自动定义一个 constructor 属性;
      • constructor 属性指向 function;
  • 若 function 为 constructor function;
    • 根据 constructor function 创建的实例自动定义一个 [[prototype]],
    • 实例的 [[prototype]] 指向 constructor function 的 prototype 属性;
    • 不同实例指向同一个 prototype 属性;
    • [[prototype]] 在浏览器中往往使用 __proto__ 属性;
  • 因为构造函数也有可能是另一个构造函数的示例,从而导致原型链的形成;

prototype 机制

对象属性的访问流程
  • 一个属性被访问;
  • 首先根据其名称在实例本身进行搜索;
  • 若未找到,再从实例指向的 prototype 中进行搜索;
重写 prototype 问题
// 对象实例仅有对 prototype 的引用
function Person() {}
let friend = new Person();
Person.prototype = {
constructor: Person,
name: "Nicholas",
age: 29,
job: "Software Engineer",
sayName() {
console.log(this.name);
},
};
friend.sayName(); // error

Dynamic Nature of Prototypes

原型链的终点
  • Object.prototype 对象;

常用 API

创建对象
// 创建一个 me 对象, 并将 me.prototype 指向 person
const me = Object.create(person);
操作属性
// 定义属性种类
// 使用 defineProperty() 定义对象属性
// 三个参数依次为对象, 属性名, 属性描述
Object.defineProperty(object1, "property1", {
value: 42,
writable: false,
});
// 定义多个属性种类
const object1 = {};
Object.defineProperties(object1, {
property1: {
value: 42,
writable: true,
},
property2: {},
});

// 获取属性描述
const descriptor1 = Object.getOwnPropertyDescriptor(object1, "property1");
console.log(descriptor1.configurable);
console.log(descriptor1.value);
// 获取 object1 所有属性名
console.log(Object.getOwnPropertyNames(object1));
// 获取 object1 所有 symbol 属性
const objectSymbols = Object.getOwnPropertySymbols(object1);

// 合并属性
// 将可枚举的属性浅复制, 合并至目标 object
const target = { a: 1, b: 2 };
const source = { b: 4, c: 5 };
const returnedTarget = Object.assign(target, source); // Object { a: 1, b: 4, c: 5 }

// 判断是否具有实例属性
const object1 = {};
object1.property1 = 42;
// 判断 object1 实例是否具有 property1 属性
console.log(object1.hasOwnProperty("property1"));
对象判等
// === 的缺陷
// 不同的 0 看作相等
console.log(+0 === -0); // true
// 不同的 NaN 看作不相等
console.log(NaN === NaN); // false
// 使用 is()
console.log(Object.is(-0, 0)); // false
console.log(Object.is(NaN, NaN)); // true
原型
const bar = new Bar();
// 检验 bar 实例指向的 [[prototype]] 是否是 Bar 的 prototype 属性
console.log(Bar.prototype.isPrototypeOf(bar)); // true
// 获得 bar 实例的 [[prototype]] 属性
Object.getPrototypeOf(bar);
// 设置 bar 实例的 [[prototype]] 属性
Object.setPrototypeOf(bar, { foo: "bar" });
console.log(bar.foo); // "bar"
枚举
const object1 = {
a: "somestring",
b: 42,
c: false,
};
// 获取 object 所有可被枚举的属性名数组
console.log(Object.keys(object1)); // ["a", "b", "c"]
// 获取 object 所有可被枚举的属性值数组
console.log(Object.values(object1)); //["somestring", 42, false]
// 获取 object 所有可被枚举的属性数组
for (const [key, value] of Object.entries(object1)) {
console.log(`${key}: ${value}`);
}
in 操作符
// 判断对象是否具有属性, 包括实例属性和原型属性
function Person() {}
Person.prototype.name = "Nicholas";
let person1 = new Person();
console.log("name" in person1); // true

// 判断对象是否具有原型属性
!object.hasOwnProperty(prop) && prop in object;
枚举顺序
  • 以下操作无明确枚举顺序;
    • for-in;
    • Object.keys();
  • 以下操作有明确枚举顺序
    • 枚举顺序;
      • 先数字属性升序排列,
      • 后字符串和 symbol 根据插入顺序排列。
    • 方法/函数。
      • Object.getOwnPropertyNames();
      • Object.getOwnPropertySymbols;
      • Object.assign();
原型和继承关系
// instanceof 操作符
console.log(instance instanceof Object); // true
console.log(instance instanceof SuperType); // true
// isPrototypeOf() 方法
console.log(Object.prototype.isPrototypeOf(instance)); // true
console.log(SuperType.prototype.isPrototypeOf(instance)); // true
console.log(SubType.prototype.isPrototypeOf(instance)); // true

继承

原型链

原型链
function SuperType() {
this.property = true;
}
SuperType.prototype.getSuperValue = function () {
return this.property;
};
function SubType() {
this.subproperty = false;
}
// 继承 SuperType
SubType.prototype = new SuperType();
// 新方法
SubType.prototype.getSubValue = function () {
return this.subproperty;
};

// SubType 及其示例 instance 指向 SubType.prototype, SubType.prototype 指向 SuperType.prototype
// 通过原型链访问到 SuperType.prototype.getSuperValue
let instance = new SubType();
console.log(instance.getSuperValue()); // true

原型链

默认原型
  • 任何 reference type 都继承于 Object。

prototype chaining

原型链问题
  • SuperType 属性共享,某一实例的引用值属性的修改会导致所有引用值属性的修改;
  • SubType 创建时无法传递参数至 SuperType。

盗用构造函数

语法格式
function SuperType() {
this.colors = ["red", "blue", "green"];
}
function SubType() {
// 继承 SuperType 属性
SuperType.call(this);
}
let instance1 = new SubType();
instance1.colors.push("black");
console.log(instance1.colors); // "red,blue,green,black"
let instance2 = new SubType();
console.log(instance2.colors); // "red,blue,green"
传递参数
function SuperType(name) {
this.name = name;
}
function SubType() {
// 继承 SuperType 属性并传递参数
SuperType.call(this, "Nicholas");
this.age = 29;
}
let instance = new SubType();
console.log(instance.name); // "Nicholas";
console.log(instance.age); // 29
盗用构造函数缺点
  • SuperType() 中的方法无法复用;
  • 无法使用 SuperType.Prototype 中的方法;

组合继承

组合继承
// 原型链和盗用构造函数的结合
// 使用 prototype chaining 继承 prototype 上的属性
// 使用 Constructor Stealing 继承 constructor function 上的属性
function SuperType(name) {
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function () {
console.log(this.name);
};
function SubType(name, age) {
// 继承 SuperType 属性
SuperType.call(this, name);
this.age = age;
}
// 继承 SuperType 方法
SubType.prototype = new SuperType();
SubType.prototype.sayAge = function () {
console.log(this.age);
};

组合继承

缺点
  • 调用两次 SuperType();
  • SubType 的 prototype 具有冗余属性;

原型式继承

// 等效于 Object.create(o)
function object(o) {
function F() {}
F.prototype = o;
return new F();
}

寄生式继承

  • 方法难以复用;
function createAnother(original) {
// 首先继承 original 属性
let clone = object(original);
// 创建自己实例属性
clone.sayHi = function () {
console.log("hi");
};
return clone;
}

寄生式组合继承

  • 结合寄生继承和组合继承;
  • 只调用一次 SuperType();
function Sup(name) {
this.name = name;
}

Sup.prototype.sayName = function () {
console.log(this.name);
};

function Sub(name, age) {
Sup.call(this, name);
this.age = age;
}

function MyCreate(obj) {
function fn() {}
fn.prototype = obj;
return new fn();
}

Sub.prototype = MyCreate(Sup.prototype);
Sub.prototype.sayAge = function () {
console.log(this.age);
};

const p1 = new Sup("kxr");
const p2 = new Sub("kxh", 25);
p1.sayName(); // kxr
p2.sayName(); // kxh
p2.sayAge(); // 25

最佳实践

不可修改的对象

  • 使用 Object.freeze();
  • 定义 object writable 属性;
  • 定义 object set 属性;

数据劫持

使用 Object.defineProperty()
  • 通过 Object.defineProperty() 和订阅者模式实现响应式;
  • 通过遍历 object 属性,在 getter 和 setter 中添加订阅逻辑;
  • 修改 object 属性,触发 setter 对应订阅逻辑;
const obj = {
name: "刘逍",
age: 20,
};
const p = {};
for (let key in obj) {
Object.defineProperty(p, key, {
get() {
console.log(`${key}属性被读取`);
return obj[key];
},
set(val) {
console.log(`${key}属性被修改`);
obj[key] = val;
},
});
}
使用 Proxy 和 Reflect
  • 使用 Proxy 创建 obj 代理;
  • 通过 Reflect 修改 obj;
const obj = {
name: "刘逍",
age: 20,
};

const p = new Proxy(obj, {
get(target, propName) {
console.log(`${propName}属性被读取`);
return Reflect.get(target, propName);
},

set(target, propName, value) {
console.log(`${propName}属性被修改`);
Reflect.set(target, propName, value);
},

deleteProperty(target, propName) {
console.log(`${propName}属性被删除`);
return Reflect.deleteProperty(target, propName);
},
});

a == 1 && a == 2 && a == 3

重写 toString() 或 valueOf()
  • 对于数组和对象皆可以;
let a = {
i: 1,
toString: function () {
return a.i++;
},
};
console.log(a == 1 && a == 2 && a == 3); // true

let a = [1, 2, 3];
a.toString = a.shift;
console.log(a == 1 && a == 2 && a == 3); // true
使用 Object.defineProperty()
var _a = 1;
Object.defineProperty(globalThis, "a", {
get: function () {
return _a++;
},
});
console.log(a === 1 && a === 2 && a === 3); //true

复杂的原型链

  • 实例的 __proto__ 指向构造函数的 prototype;
  • 构造函数也是一个函数对象,其 __proto__ 指向 Function.prototype;
  • 原型对象是一个普通对象,其 __proto__ 指向 Object.prototype;
  • Object 本身也是一个函数对象,其 __proto__ 指向 Function.prototype;
  • Object 的 prototype 也有 __proto__ 指向 null;
person1.__proto__ === Person.prototype;
Person.__proto__ === Function.prototype;
Person.prototype.__proto__ === Object.prototype;
Object.__proto__ === Function.prototype;
Object.prototype.__proto__ === null;

原型链