共计 12396 个字符,预计需要花费 31 分钟才能阅读完成。
对象、类与面向对象编程
对象
一组属性的无序汇合
属性
-
类型
- 数据属性 value、writable
- 拜访器属性 getter、setter 至多有一
-
定义
- . 操作符:默认可配置、可枚举、可写(数据属性)
- Object.defineProperty/defineProperties:默认不可配置、不可枚举、只读
configurable:false。不可删除、不可逆。无奈批改其余个性的值(除 writable 从 true 改为 false)
读取个性:Object.getOwnPropertyDescriptor / Object.getOwnPropertyDescriptors(ES8)
属性名有后缀下划线:通常示意该属性不心愿在对象办法的内部被拜访
合并对象
Object.assign 复制源对象的可枚举且自有的属性到指标对象
- 指标只有 getter 源对象无奈将同名属性复制到指标对象 => 报错
- 指标有 setter,源对象通过执行 [[Get]] 获取同名属性值,给到指标对象的[[Setter]]
中途抛错 => 实现局部复制
新增的定义和操作对象的语法糖
- 属性简写(变量名和属性名同名)
- 可计算属性:以字面量模式定义 计算失败 => 定义失败
- 简写办法名:省略 function 关键字(默认匿名)
对象解构
let {name, age} = person; // 常见
let {name, job='Software Engineer'} = person; // 未获取到时的默认值
let {name: personName, age: personAge} = person; // 外部申明
({name: personName, age: personAge} = person); // 对外部申明的赋值
-
嵌套解构
嵌套的属性,属性值是对象类型或子类型
-
局部解构
过程出错,实现局部赋值
-
函数参数以解构模式
function foo(f1, {name, age}, f2) {}
第二个参数存在名为 name 和 age 的属性
对象创立
封装、防止冗余
-
工厂模式
工厂函数:接管(特定)参数,拼装对象,返回对象
-
构造函数模式(申明或表达式都可)
函数:接管特定参数,拼装 this,可无显式返回(默认为 this 指向的新对象)
结构调用:1)创建对象;2)连贯对象 [[prototype]] 到函数的 prototype 对象;3)对象赋值给 this;4)执行函数中的代码;5)返回指定对象或默认 this。
可通过 instanceof 确定援用类型
问题:定义的对象如果存在类型为 function 的性能雷同的属性,屡次调用会产生多个 function 实例。
-
原型模式
利用函数的 prototype 属性(蕴含了它的实例可共享的属性和办法)。
原型:函数申明后会主动获取 prototype 属性,默认有一个键为 constructor,值指回函数。
instanceof 理论是查函数的 prototype 对象。
- xxx.isPrototypeOf(obj):obj 的原型链上是不是存在一个 xxx 指向的对象
- Object.getPrototypeOf(obj):可用来获取一个对象的原型链
- Object.setPrototypeOf(obj, proObj):重写一个对象(obj)的原型继承关系——> 可能影响性能,也可能影响拜访了原 [[prototype]] 对象的代码
Object.create:创建对象,并指定原型。
原型层级遮蔽。
查属性:
- hasOwnProperty:本身属性
- in 和 for-in:查链、可枚举
- Object.keys():不查链、可枚举
-
Object.getOwnPropertyNames():不管是否可枚举,非符号键
Object.getOwnPropertySymbols():不管是否可枚举,符号键
枚举程序:
- for-in / Object.keys() 取决于浏览器
- getOPN、getOPS、Object.assign:升序枚举数值键、插入程序枚举字符串和符号键
-
对象迭代
Object.values():值数组
Object.entreis():键 / 值对数组
以上两个办法符号键都会疏忽。
可通过原型封装性能 => 重写.prototype 属性 => 会失落 constructor(能够手动补)
批改原型对象的两种形式:
1)整个重写,从新定义一个对象并赋值给.prototype
2)增 / 改原型对象属性 / 值
js 原生援用类型的实现基于原型模式。如需批改原生援用类型的原型对象 => 更举荐的做法:创立一个类继承原生类型;间接批改可能导致命名抵触、意外重写。
原型存在的问题。共享属性中含有援用类型的数据成员(非函数)
继承
-
原型链
实例和原型对象之间结构了援用连贯关系
①默认原型:Object.prototype
②原型与继承:
instanceof 实例的原型链上呈现过构造函数.prototype 关联的对象
isPrototypeOf 判断两个对象之间的关系
③办法笼罩(子类笼罩父类):
子类.prototype = 父类 new 进去的实例
再批改单个属性(子类.prototype)
④存在的问题:子类实例化时不能给父类构造函数传参
-
盗用构造函数:伪多态,没有原型链
在子类构造函数中通过 call 或 apply 调用父类函数(非结构调用)
①解决原型链的问题:可传参给父类函数
②问题:
a. 无链,子类产生的实例无奈对父类及其原型对象利用 instanceof 和 isPrototypeOf 办法
b. 必须在构造函数中定义方法(属于实例的办法),函数不能重用(与构造函数模式一样的问题)
-
组合继承:伪多态 + 原型
①在子类构造函数中通过 call 或 apply 调用父类函数
②重写子类原型对象(用 new 父类产生的实例赋值)
解决的问题:instanceof、isPrototypeOf 可用;可增加 / 批改原型办法
-
原型式继承:间接关联两个对象
相似间接应用 Object.create
适宜不须要构造函数的场合
-
寄生式继承:相似工厂
相似工厂函数,但不是用裸的 Object,以某种形式获得对象(如 new 等返回新对象的函数),对此对象加属性或办法以加强性能,并返回对象。
function createAnother(original) {let clone = Object.create(original); clone.xx = xxx; return clone; }
同样适宜次要关注对象,而不在乎类型和构造函数的场景。
存在的问题:必须在构造函数中定义方法(属于实例的办法),函数不能重用(与构造函数模式一样的问题)
-
寄生式组合继承:伪多态 +Object.create 代替 new 父类(优化 3)
用 Object.create()替换 new 父类实例来重写子类的原型对象(优化 3,舍去 new 中其余多余操作)
function inheritatePrototype(subT, superT) {let proto = Object.create(superT.prototype); proto.constructor = subT; subT.prototype = proto; }
/*
* 属性
*/
let o = {b: 'test B'};
// Object.defineProperty(o, "name", {
// value: 0,
// get: function() {
// return 1;
// },
// set: function(val) {
// this.name = val;
// },
// writable: true
// });
// console.log(Object.getOwnPropertyDescriptor(o, "name") );
// TypeError: Invalid property descriptor. Cannot both specify accessors and a value or writable attribute
Object.defineProperty(o, "name", {get: function() {return this.b;},
set: function(val) {this.name = val;}
});
console.log(Object.getOwnPropertyDescriptor(o, "name") );
// {// get: [Function: get],
// set: [Function: set],
// enumerable: false,
// configurable: false
// }
console.log(Object.getOwnPropertyDescriptor(o, "name").value ); // undefined
console.log(Object.getOwnPropertyDescriptor(o, "b") );
// {
// value: 'test B',
// writable: true,
// enumerable: true,
// configurable: true
// }
console.log(Object.getOwnPropertyDescriptors(o) );
// {
// b: {
// value: 'test B',
// writable: true,
// enumerable: true,
// configurable: true
// },
// name: {// get: [Function: get],
// set: [Function: set],
// enumerable: false,
// configurable: false
// }
// }
/*
* 合并对象
*/
let dest, src, result;
// 1. 指标对象只有同名的 set,混入源对象的同名 get
// 会先执行源对象的 get 获取值,把值传递给指标对象的 set 并执行。// 无奈通过指标对象获取值(指标对象无 get)dest = {set a(val) {console.log( `Invoked dest setter with param ${val}` );
}
};
src = {get a() {console.log( 'Invoked src getter');return 1;
}
};
Object.assign(dest, src);
console.log(dest);
// Invoked src getter
// Invoked dest setter with param undefined
// {a: [Setter] }
// 2. 指标对象只有同名的 get,混入源对象的同名 set 会报错
// Object.assign(src, dest);
// TypeError: Cannot set property a of #<Object> which has only a getter
// 3. 指标对象有 get 和 set,混入的源对象只有同名 set
// 会先执行源对象的 get 获取值(undefined),执行指标对象的 set
src = {get a() {console.log( 'Invoked src getter');
return 1;
},
set a(val) {console.log( `Invoked src setter with param ${val}` );
}
};
Object.assign(src, dest); // Invoked src setter with param undefined
console.log('src', src); // src {a: [Getter/Setter] }
console.log('src.a', src.a);
// Invoked src getter
// src.a 1
// 4. 指标和源都只有 get
dest = {get a() {console.log( 'dest');
}
};
src = {get a() {console.log( 'src');
}
};
// Object.assign(dest, src); // TypeError: Cannot set property a of #<Object> which has only a getter
// 通过 setter 察看笼罩的过程
dest = {set id(x) {console.log( x);
}
};
Object.assign(dest, { id: 1}, {id: 2}, {id: 3});
// 1
// 2
// 3
dest = {};
src = {
a: 'foo',
get b() {throw new Error();
},
c: 'bar'
};
try{Object.assign( dest, src);
} catch(e) {}
console.log(dest); // {a: 'foo'}
/*
* 新增的定义和操作对象的语法糖
*/
let nameKey="name";
// let o2 = {// [nameKey]: "lily",
// [ageKey]: 12
// };
// ReferenceError: ageKey is not defined
let o2;
try{
o2 = {[nameKey]: "lily",
[ageKey]: 12
};
} catch(e) {}
console.log(o2); // undefined
o2 = {[nameKey]: "lily",
};
console.log(o2); // {name: 'lily'}
/*
* 对象解构
*/
let person = {
name: 'aa',
age: 27
};
let {name, age} = person;
console.log(name, age); // aa 27
let {name: nameValue, age: ageValue} = person;
console.log(nameValue, ageValue); // aa 27
/*
let {job} = person;
// 相当于
// let job;
// job = person.job;
console.log(job); // undefined
*/
let {job = 'OA'} = person;
// 相当于
// let job;
// job = person.job || 'OA';
console.log(job) ;
let gender;
// {gender} = person; // SyntaxError: Unexpected token '='
({gender} = person);
console.log(gender); // undefined
let personName, personAge, personBar;
try {({name: personName, foo: {bar: personBar}, age: personAge} = person);
} catch(e) {}
console.log(personName, personBar, personAge); // aa undefined undefined
function printPerson(foo, {name, age}, bar) {console.log( arguments);
console.log(name, age);
}
function printPerson2(foo, {name: personName, age: personAge}, bar) {console.log( arguments);
console.log(personName, personAge);
}
printPerson("1st", person, '2nd');
// [Arguments] {'0': '1st', '1': { name: 'aa', age: 27}, '2': '2nd' }
// aa 27
printPerson2("1st", person, '2nd');
// [Arguments] {'0': '1st', '1': { name: 'aa', age: 27}, '2': '2nd' }
// aa 27
/*
* 对象创立 继承
*/
function Foo() {}
Foo.prototype = {};
let f = new Foo();
console.log(f.contructor === Foo); // false
console.log(f instanceof Foo); // true
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = new Function("console.log(this.name)");
}
let p1 = new Person("kk", 12, "farmer");
p1.sayName(); // kk
function Student() {}
Student.prototype = {
name: 'class1',
friends: [1, 2, 3]
};
let s1 = new Student();
let s2 = new Student();
s1.friends = [2, 3, 4];
console.log(s1.friends === s2.friends); // false
console.log(s1.friends, s2.friends); // [2, 3, 4] [1, 2, 3]
let k1 = Symbol('k1'), k2 = Symbol('k2');
let o1 = {
1: 1,
first: 'first',
[k1]: 'k1',
second: 'second',
0: 0
};
o1[k2] = 'k2';
o1[3] = 3;
o1.third = 'third';
console.log(Object.getOwnPropertyNames( o1) );
// ['0', '1', '3', 'first', 'second', 'third']
console.log(Object.getOwnPropertySymbols( o1) );
// [Symbol(k1), Symbol(k2) ]
Object.assign(o1, { 2: 2} );
console.log(Object.getOwnPropertyNames( o1) );
// ['0', '1', '2', '3', 'first', 'second', 'third']
类
-
类申明与类表达式
与函数的区别:
①函数申明可晋升,类定义不行
②函数受函数作用域限度,类受块作用域限度
③默认状况下,类块中的代码在严格模式下执行
④类定义体中可蕴含 constructor、实例办法、获取函数(get)、设置函数(set)和动态类办法(static 关键字)。除了动态类办法,其余都定义在原型对象上。
类表达式的名称:
①可通过类表达式赋值的变量的 name 属性获取
②不能在类表达式作用域内部拜访这个标识符
class 的所有办法(包含静态方法和实例办法)都没有原型对象 prototype,所以也没有[[construct]],不能应用 new 来调用。
-
constructor 类构造函数
new 调用类时,默认调用 constructor 函数。constructor 函数 必须 应用 new 操作符调用。
对于定义:
如未定义,默认为空函数体。
对于返回:
如返回 null、symbol、number、string、boolean,则返回刚创立的对象
如返回 {} 或其余非空对象,则返回该对象。
默认返回刚创立的对象。
实例.constructor 即 类.prototype.contructor
类.constructor 即 Function.prototype.constructor(类是函数的实例)
-
实例成员、原型成员、类成员
-
实例成员
在 constructor 中操作 this,或在其余原型函数中操作 this。
-
原型成员
在 class 块中定义的办法为原型办法,不能增加成员数据(原始值或对象)
获取或设置拜访器也反对(get、set)
-
动态类成员
适宜作实例工厂
-
其余:
在类定义内部可通过批改原型对象减少成员数据,但不举荐(在共享指标(原型、类)上增加可变数据成员),如
Person.prototype.name
。反对生成器函数。一般对象,包含函数原型对象也反对生成器函数。
-
-
继承
-
语法 extends 关键字
可继承 class 或 function(向后兼容)等任何领有 [[construct]] 和 prototype 的对象
-
constructor、HomeObject 和 super()
外部个性 [[HomeObject]] 始终指向定义该办法的对象(类)
constructor:
- 不显式定义:new 调用时默认会调 super(),并传入传给派生类的全副参数
-
显式定义:
必须调 super()或者显式返回一个对象
要应用 this 必须先调 super()
super 语法:
- 只能在派生类的 constructor 和办法中应用
- 不能独自援用,必须调构造函数或援用静态方法或原型办法
- 调用 super()后,将返回的实例赋值给 this。如需传参给父类构造函数,需手动
-
“ 伪 ” 对象基类
- 不能间接实例化(应用 new.target 进行拦挡)
- 子类必须定义某个办法(在父类构造函数中用 this. 办法名进行检测)
-
继承内置类型
如果有实例办法返回新对象实例,默认状况下,新实例与调用实例办法的实例类型统一,如需批改这个行为,可笼罩 Symbol.species 拜访器。如:
class ... {static get [Symbol.species]() {return Array;} }
-
类混入
连缀多个混入元素
如:Person-(继承)->C->B->A
形式:
- 连缀调用
-
写一个辅助函数,把嵌套调用开展
function mix(BaseClass, ...Mixins) {return Mixins.reduce((accumulator, current)=>current(accumulator), BaseClass); }
不举荐应用:
软件设计准则——复合胜过继承。在代码设计中提供极大灵活性。
-
/* class 定义不能被晋升
console.log(A);
// node - ReferenceError: Cannot access 'A' before initialization
// chrome - ReferenceError: A is not defined
class A {}
*/
/* 作用域限度不一样
{function FunctionDeclaration() {}
class ClassDelaration {}}
console.log(FunctionDeclaration); // [Function: FunctionDeclaration]
console.log(ClassDelaration); // ReferenceError: ClassDelaration is not defined
*/
/* 类表达式赋值给变量后,类表达式的名称无奈在类表达式作用域内部拜访
let Person = class PersonName {identify() {console.log( Person.name, PersonName.name);
}
}
let p = new Person();
p.identify(); // PersonName PersonName
console.log(p); // PersonName {}
console.log(Person.name); // PersonName
console.log(PersonName); // ReferenceError: PersonName is not defined
*/
/*
* constructor、实例成员、原型成员、类成员
*/
class Person {set name(newName) { // name_会增加到实例上,name 是在原型对象上
this.name_ = newName;
}
get name() {return this.name_;}
static locate() {console.log( 'class', this);
}
static className() {console.log( 'Person');
}
}
let p1 = new Person();
// p1.constructor(); // TypeError: Class constructor Person cannot be invoked without 'new'
let p2 = new p1.constructor();
console.log(p2); // Person {}
console.log(p2 instanceof Person); // true
console.log(Person); // [class Person]
console.log(typeof Person); // function
let p3 = new Person.constructor();
console.log(p3.constructor === Person); // false
console.log(p3 instanceof Person); // false
console.log(p3 instanceof Person.constructor); // true
console.log(p1.constructor === Person.constructor); // false
console.log(p1.constructor); // [class Person]
console.log(Person.constructor); // [Function: Function]
console.log(p1.constructor === Person.prototype.constructor); // true
Person.locate(); // class [class Person]
let p = new class Foo {constructor(x) {console.log( x);
}
}('bar');
console.log(p);
// bar
// Foo {}
/*
* 继承
*/
function Job() {}
class Engineer extends Job {}
let e = new Engineer();
console.log(e instanceof Engineer); // true
console.log(e instanceof Job); // true
class Vehicle {constructor(licensePlate) {
this.licensePlate = licensePlate;
if(new.target === Vehicle) { // "伪形象基类(不能间接实例化)"
throw new Error('Vehicle cannot be directly instantiated');
}
if(!this.foo) { // 子类必须定义某个办法
throw new Error('Inheriting class must define foo()');
}
}
identifyPrototype(id) {console.log( id, this);
}
static identifyClass(id) {console.log( id, this);
}
}
class Bus extends Vehicle {// constructor() {} // ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor
// 定义的 constructor 函数体内必须得调用 super() 或者 返回一个对象; 如果要拜访 this,必须先调用 super();
// constructor() { return {};}
/*constructor() {
// this.name = "test"; // ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor
super();
console.log(this instanceof Vehicle);
}*/
static identifyClass(id) {console.log( id, 'overwrite', this);
}
foo() {}
}
// let v = new Vehicle(); // Error: Vehicle cannot be directly instantiated
let b = new Bus('1337H4X'); // Error: Inheriting class must define foo()
// new Bus(); // true
b.identifyPrototype('bus'); // bus Bus {licensePlate: '1337H4X'} // bus Bus {}
// v.identifyPrototype('vehicle'); // vehicle Vehicle {}
Bus.identifyClass('bus'); // bus overwrite [class Bus extends Vehicle]
Vehicle.identifyClass('vehicle'); // vehicle [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');
}
};
function mix(BaseClass, ...Minxins) {return Minxins.reduce((accumulator, current) => current(accumulator), BaseClass);
}
class Bus1 extends mix(Vehicle, BazMixin, BarMixin, FooMixin){};
let bb1 = new Bus1();
bb1.foo(); // foo
bb1.bar(); // bar
bb1.baz(); // baz
class TestSuper {foo() {console.log( 'instance super.foo');
}
static foo() {console.log( 'super.foo');
}
}
class TestSub extends TestSuper{foo() {super.foo();
console.log('instance sub.foo');
}
static foo() {super.foo();
console.log('sub.foo');
}
}
let sub = new TestSub();
sub.foo();
// instance super.foo
// instance sub.foo
TestSub.foo();
// super.foo
// sub.foo