js高级程序设计阅读第八章

62 阅读10分钟

对象

数据属性

Object.defineProperty()

数据属性包含一个保存数据值的位置。值会从这个位置读取,也会写入到这个位置。数据属性有 4
个特性描述它们的行为。
 [[Configurable]]:表示属性是否可以通过 delete 删除并重新定义,是否可以修改它的特
性,以及是否可以把它改为访问器属性。默认情况下,所有直接定义在对象上的属性的这个特
性都是 true,如前面的例子所示。
 [[Enumerable]]:表示属性是否可以通过 for-in 循环返回。默认情况下,所有直接定义在对
象上的属性的这个特性都是 true,如前面的例子所示。
 [[Writable]]:表示属性的值是否可以被修改。默认情况下,所有直接定义在对象上的属性的
这个特性都是 true,如前面的例子所示。
 [[Value]]:包含属性实际的值。这就是前面提到的那个读取和写入属性值的位置。这个特性
的默认值为 undefined。
在像前面例子中那样将属性显式添加到对象之后,[[Configurable]][[Enumerable]][[Writable]]都会被设置为 true,而[[Value]]特性会被设置为指定的值。比如:
let person = { 
 name: "Nicholas" 
}; 
这里,我们创建了一个名为 name 的属性,并给它赋予了一个值"Nicholas"。这意味着[[Value]]
特性会被设置为"Nicholas",之后对这个值的任何修改都会保存这个位置。
要修改属性的默认特性,就必须使用 Object.defineProperty()方法。这个方法接收 3 个参数:
要给其添加属性的对象、属性的名称和一个描述符对象。最后一个参数,即描述符对象上的属性可以包
含:configurable、enumerable、writable 和 value,跟相关特性的名称一一对应。根据要修改
的特性,可以设置其中一个或多个值。比如:
let person = {}; 
Object.defineProperty(person, "name", { 
 writable: false, 
 value: "Nicholas" 
}); 
console.log(person.name); // "Nicholas" 
person.name = "Greg"; 
console.log(person.name); // "Nicholas" 
这个例子创建了一个名为 name 的属性并给它赋予了一个只读的值"Nicholas"。这个属性的值就
不能再修改了,在非严格模式下尝试给这个属性重新赋值会被忽略。在严格模式下,尝试修改只读属性
的值会抛出错误。

访问器属性

[[Configurable]]:表示属性是否可以通过 delete 删除并重新定义,是否可以修改它的特
性,以及是否可以把它改为数据属性。默认情况下,所有直接定义在对象上的属性的这个特性
都是 true。
 [[Enumerable]]:表示属性是否可以通过 for-in 循环返回。默认情况下,所有直接定义在对
象上的属性的这个特性都是 true。
 [[Get]]:获取函数,在读取属性时调用。默认值为 undefined。
 [[Set]]:设置函数,在写入属性时调用。默认值为 undefined。
访问器属性是不能直接定义的,必须使用 Object.defineProperty()。下面是一个例子:
// 定义一个对象,包含伪私有成员 year_和公共成员 edition 
let book = { 
 year_: 2017, 
 edition: 1 
}; 
Object.defineProperty(book, "year", { 
 get() { 
 return this.year_; 
 }, 
 set(newValue) { 
 if (newValue > 2017) { 
 this.year_ = newValue; 
 this.edition += newValue - 2017; 
 } 
 } 
}); 
book.year = 2018; 
console.log(book.edition); // 2


定义多个属性Object.defineProperties()方法

读取属性的特性 Object.getOwnPropertyDescriptor()方法可以取得指定属性的属性描述符,对于访问器属性包含configurable、enumerable、get 和 set 属性,对于数据属性包含 configurable、enumerable、writable 和 value 属性。

也可以通过Object.getOwnPropertyDescriptors()静态方法一次性返回所有属性

合并对象

Object.assign()

通过get set 修饰符 在获取设置值前做处理

dest = {}; 
result = Object.assign(dest, { a: 'foo' }, { b: 'bar' }); 
console.log(result); // { a: foo, b: bar } 
/** 
 * 获取函数与设置函数
 */ 
dest = { 
 set a(val) { 
 console.log(`Invoked dest setter with param ${val}`); 
 } 
}; 
src = { 
 get a() { 
 console.log('Invoked src getter'); 
 return 'foo'; 
 } 
}; 
Object.assign(dest, src); 
// 调用 src 的获取方法
// 调用 dest 的设置方法并传入参数"foo" 
// 因为这里的设置函数不执行赋值操作
// 所以实际上并没有把值转移过来
console.log(dest); // { set a(val) {...} } 



Object.is() 比较相等

可计算属性

当成表达式赋值

const nameKey = 'name'; 
const ageKey = 'age'; 
const jobKey = 'job'; 
let person = {}; 
person[nameKey] = 'Matt';
person[ageKey] = 27; 
person[jobKey] = 'Software engineer'; 
console.log(person); // { name: 'Matt', age: 27, job: 'Software engineer' }

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' }

通过嵌套解构浅拷贝

let person = { 
 name: 'Matt', 
 age: 27, 
 job: { 
 title: 'Software engineer' 
 } 
}; 
let personCopy = {}; 
({ 
 name: personCopy.name, 
 age: personCopy.age, 
 job: personCopy.job 
} = person); 
// 因为一个对象的引用被赋值给 personCopy,所以修改
// person.job 对象的属性也会影响 personCopy 
person.job.title = 'Hacker' 
console.log(person); 
// { name: 'Matt', age: 27, job: { title: 'Hacker' } } 
console.log(personCopy); 
// { name: 'Matt', age: 27, job: { title: 'Hacker' } } 

arguments转数组

var args = [].slice.call(arguments);  // 方式一
var args = Array.prototype.slice.call(arguments); // 方式二

// 下面是 es6 提供的语法
let args = Array.from(arguments)   // 方式一
let args = [...arguments]; // 方式二

new操作符做了什么

(1) 在内存中创建一个新对象。
(2) 这个新对象内部的[[Prototype]]特性被赋值为构造函数的 prototype 属性。
(3) 构造函数内部的 this 被赋值为这个新对象(即 this 指向新对象)。
(4) 执行构造函数内部的代码(给新对象添加属性)。
(5) 如果构造函数返回非空对象,则返回该对象;否则,返回刚创建的新对象

构造函数也是函数

// 作为构造函数 
let person = new Person("Nicholas", 29, "Software Engineer"); 
person.sayName(); // "Nicholas" 
// 作为函数调用
Person("Greg", 27, "Doctor"); // 添加到 window 对象
window.sayName(); // "Greg" 
// 在另一个对象的作用域中调用
let o = new Object(); 
Person.call(o, "Kristen", 25, "Nurse"); 
o.sayName(); // "Kristen"

Object.getPrototypeOf()

setPrototypeOf()

hasOwnProperty() 实例使用 确定属性是在实例上还是在原型对象上

Object.getOwnPropertyDescriptor()。 原型对象使用 确定属性是在实例上还是在原型对象上

hasPrototypeProperty

getOwnPropertyNames 获取实例所有属性

let keys = Object.getOwnPropertyNames(Person.prototype); 
console.log(keys); // "[constructor,name,age,job,sayName]"

Object.getOwnPropertyNames()

Object.getOwnPropertySymbols()

Object.getOwnPropertyNames()、Object.getOwnPropertySymbols()和 Object.assign() 的枚举顺序是确定性的。先以升序枚举数值键,然后以插入顺序枚举字符串和符号键。

let k1 = Symbol('k1'), 
 k2 = Symbol('k2'); 
let o = { 
 1: 1, 
 first: 'first', 
 [k1]: 'sym2', 
 second: 'second', 
 0: 0 
}; 
o[k2] = 'sym2'; 
o[3] = 3; 
o.third = 'third'; 
o[2] = 2; 
console.log(Object.getOwnPropertyNames(o)); 
// ["0", "1", "2", "3", "first", "second", "third"] 
console.log(Object.getOwnPropertySymbols(o)); 
// [Symbol(k1), Symbol(k2)]

原型的问题function Person() {}

Person.prototype = { 
 constructor: Person, 
 name: "Nicholas", 
 age: 29, 
 job: "Software Engineer", 
 friends: ["Shelby", "Court"], 
 sayName() { console.log(this.name); } }; 
 let person1 = new Person(); 
 let person2 = new Person(); 
 person1.friends.push("Van"); 
 console.log(person1.friends); // "Shelby,Court,Van" 
 console.log(person2.friends); // "Shelby,Court,Van" 
 console.log(person1.friends === person2.friends); // true

例子

function SuperType() {
this.property = true;
}
SuperType.prototype.getSuperValue = function () {
    return this.property;
};


function SubType() {
    this.subproperty = false;
}

console.log(new SuperType());
// 继承 SuperType  
SubType.prototype = new SuperType();


SubType.prototype.getSubValue = function () {
    return this.subproperty;
};


let instance = new SubType();

console.log(instance.getSuperValue()); // true


继承
console.log(new SubType().__proto__.constructor === SuperType);// true

以上代码定义了两个类型:SuperType 和 SubType。这两个类型分别定义了一个属性和一个方法。 这两个类型的主要区别是 SubType 通过创建 SuperType 的实例并将其赋值给自己的原型 SubTtype. prototype 实现了对 SuperType 的继承。这个赋值重写了 SubType 最初的原型,将其替换为 SuperType 的实例。这意味着 SuperType 实例可以访问的所有属性和方法也会存在于 SubType. prototype。这样实现继承之后,代码紧接着又给 SubType.prototype,也就是这个 SuperType 的 实例添加了一个新方法。最后又创建了 SubType 的实例并调用了它继承的 getSuperValue()方法。 图 8-4 展示了子类的实例与两个构造函数及其对应的原型之间的关系。

这个例子中实现继承的关键,是 SubType 没有使用默认原型,而是将其替换成了一个新的对象。这个 新的对象恰好是 SuperType 的实例。这样一来,SubType 的实例不仅能从 SuperType 的实例中继承属性 和方法,而且还与 SuperType 的原型挂上了钩。于是 instance(通过内部的[[Prototype]])指向 SubType.prototype,而 SubType.prototype(作为 SuperType 的实例又通过内部的[[Prototype]]) 指向 SuperType.prototype。注意,getSuperValue()方法还在 SuperType.prototype 对象上, 而 property 属性则在 SubType.prototype 上。这是因为 getSuperValue()是一个原型方法,而 property 是一个实例属性。SubType.prototype 现在是 SuperType 的一个实例,因此 property 才会存储在它上面。还要注意,由于 SubType.prototype 的 constructor 属性被重写为指向 SuperType,所以 instance.constructor 也指向 SuperType。 原型链扩展了前面描述的原型搜索机制。我们知道,在读取实例上的属性时,首先会在实例上搜索 这个属性。如果没找到,则会继承搜索实例的原型。在通过原型链实现继承之后,搜索就可以继承向上, 搜索原型的原型。对前面的例子而言,调用 instance.getSuperValue()经过了 3 步搜索:instance、 SubType.prototype 和 SuperType.prototype,最后一步才找到这个方法。对属性和方法的搜索会 一直持续到原型链的末端

isPrototypeOf()

使用 isPrototypeOf()方法。原型链中的每个原型都可以调用这个 方法,如下例所示,只要原型链中包含这个原型,这个方法就返回 true: console.log(Object.prototype.isPrototypeOf(instance)); // true console.log(SuperType.prototype.isPrototypeOf(instance)); // true console.log(SubType.prototype.isPrototypeOf(instance)); // true

!

// prototype 原型 // constructor 构造函数 // 实例的[[Prototype]] 指向构造函数的原型对象

盗用构造函数

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;
    this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function () {
    console.log(this.name);
};
function SubType(name, age) {
    // 继承属性
    SuperType.call(this, name);
    // 得到自己的name colors
    this.age = age;
}
// 继承方法

console.log(new SuperType());
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); // "red,blue,green,black" 
instance1.sayName(); // "Nicholas"; 
instance1.sayAge(); // 29 
let instance2 = new SubType("Greg", 27);
console.log(instance2.colors); // "red,blue,green" 
instance2.sayName(); // "Greg"; 
instance2.sayAge(); // 27

原型式继承 Objcet.create()

let person = { 
 name: "Nicholas", 
 friends: ["Shelby", "Court", "Van"] 
}; 
let anotherPerson = Object.create(person, { 
 name: { 
 value: "Greg" 
 } 
}); 
console.log(anotherPerson.name); // "Greg"

寄生式组合继承

类中定义的 constructor 方法不会被当成构造函数,在对它使用 instanceof 操作符时会返回 false。但是,如果在创建实例时直接将类构造函数当成普通构造函数来 使用,那么 instanceof 操作符的返回值会反转

class Person { }
let p1 = new Person();
console.log(p1.constructor === Person); // true 
console.log(p1 instanceof Person); // true 
console.log(p1 instanceof Person.constructor); // false 
let p2 = new Person.constructor();
console.log(p2.constructor === Person); // false 
console.log(p2 instanceof Person); // false 
console.log(p2 instanceof Person.constructor); // true 

# 静态方法
state 定义在类本身身上

class Person { 
constructor() { 
// 添加到 this 的所有内容都会存在于不同的实例上
this.locate = () => console.log('instance', this); 
} 
// 定义在类的原型对象上
locate() { 
console.log('prototype', this); 
} 
// 定义在类本身上
static locate() { 
console.log('class', this); 
} 
} 
let p = new Person(); 
p.locate(); // instance, Person {} 
Person.prototype.locate(); // prototype, {constructor: ... } 
Person.locate(); // class, class Person {} 

super

在静态方法中可以通过 super 调用继承的类上定义的静态方法

class Vehicle {
static identify() {
    console.log('vehicle');
}
}
class Bus extends Vehicle {
    static identify() {
        super.identify();
    }
}
Bus.identify(); // vehicle 

super()的行为如同调用构造函数,如果需要给父类构造函数传参,则需要手动传入。

class Vehicle {
constructor(licensePlate) {
    this.licensePlate = licensePlate;
}
}
class Bus extends Vehicle {
    constructor(licensePlate) {
        super(licensePlate);
    }
}
console.log(new Bus('1337H4X')); // Bus { licensePlate: '1337H4X' }

如果在派生类中显式定义了构造函数,则要么必须在其中调用 super(),要么必须在其中返回一个对象

class Vehicle { }
class Car extends Vehicle { }
class Bus extends Vehicle {
    constructor() {
        super();
    }
}
class Van extends Vehicle {
    constructor() {
        return {};
    }
}
console.log(new Car()); // Car {} 
console.log(new Bus()); // Bus {} 
console.log(new Van()); // {} 

抽象基类

通过new.target判断是谁通过new创建实例 如果是自身 就报错不允许被创建
通过在抽象基类构造函数中进行检查,可以要求派生类必须定义某个方法
// 抽象基类
class Vehicle {
    constructor() {
        if (new.target === Vehicle) {
            throw new Error('Vehicle cannot be directly instantiated');
        }
        if (!this.foo) {
            throw new Error('Inheriting class must define foo()');
        }
        console.log('success!');
    }
}
// 派生类
class Bus extends Vehicle {
    foo() { }
}
// 派生类
class Van extends Vehicle { }
new Bus(); // success! 
new Van(); // Error: Inheriting class must define foo() 

通过 Symbol.species设置返回的类

class SuperArray extends Array { 
static get [Symbol.species]() { 
return Array; 
} 
} 
let a1 = new SuperArray(1, 2, 3, 4, 5); 
let a2 = a1.filter(x => !!(x%2)) 
console.log(a1); // [1, 2, 3, 4, 5] 
console.log(a2); // [1, 3, 5] 
console.log(a1 instanceof SuperArray); // true 
console.log(a2 instanceof SuperArray); // false

类混入

没看太懂
class Vehicle {} 
let FooMixin = (Superclass) => class extends Superclass { 
foo() { 
console.log('foo'); 
} 
}; 
let BarMixin = (Superclass) => class extends Superclass { 
bar() { 
console.log('bar'); 
} 
}; 
let BazMixin = (Superclass) => class extends Superclass { 
baz() { 
console.log('baz'); 
} 
}; 
class Bus extends FooMixin(BarMixin(BazMixin(Vehicle))) {} 
let b = new Bus(); 
b.foo(); // foo 
b.bar(); // bar 
b.baz(); // baz