对象、类与面向对象编程
对象
一组属性的无序汇合
属性
类型
- 数据属性 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 attributeObject.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 ); // undefinedconsole.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),执行指标对象的setsrc = { 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 undefinedconsole.log( 'src', src ); // src { a: [Getter/Setter] }console.log( 'src.a', src.a );// Invoked src getter// src.a 1// 4.指标和源都只有getdest = { 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// 3dest = {};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 definedlet o2;try{ o2 = { [nameKey]: "lily", [ageKey]: 12 };} catch(e) {} console.log( o2 ); // undefinedo2 = { [nameKey]: "lily",};console.log( o2 ); // { name: 'lily' }/** 对象解构*/let person = { name: 'aa', age: 27};let { name, age } = person;console.log( name, age ); // aa 27let { 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 ); // undefinedlet personName, personAge, personBar;try { ({name: personName, foo: {bar: personBar}, age: personAge} = person);} catch(e) {}console.log( personName, personBar, personAge ); // aa undefined undefinedfunction 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 27printPerson2( "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 ); // falseconsole.log( f instanceof Foo ); // truefunction 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(); // kkfunction 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 ); // falseconsole.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 definedclass 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 PersonNameconsole.log( p ); // PersonName {}console.log( Person.name ); // PersonNameconsole.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 ); // trueconsole.log( Person ); // [class Person]console.log( typeof Person ); // functionlet p3 = new Person.constructor();console.log( p3.constructor === Person ); // falseconsole.log( p3 instanceof Person ); // falseconsole.log( p3 instanceof Person.constructor ); // trueconsole.log( p1.constructor === Person.constructor ); // falseconsole.log( p1.constructor ); // [class Person]console.log( Person.constructor ); // [Function: Function]console.log( p1.constructor === Person.prototype.constructor ); // truePerson.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 ); // trueconsole.log( e instanceof Job ); // trueclass 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 instantiatedlet b = new Bus('1337H4X'); // Error: Inheriting class must define foo() // new Bus(); // trueb.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(); // foobb1.bar(); // barbb1.baz(); // bazclass 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.fooTestSub.foo();// super.foo// sub.foo