对象、类与面向对象编程

属性的类型

内部属性用两个中括号如[ [ Enumerable ] ]
开发者不能直接访问

数据属性

数据属性:数据属性包含一个保存数据值的位置。值会从这个位置读取,也会写入到这个位置。数据属性有四个特性描述他们的行为。


[[Configurable]]:表示睡醒是否可以通过delete删除并重新定义,是否可以修改,是否可以把它修改为访问器属性
[[Enumberable]]: 属性是否可以通过for-in循环
[[Writable]]: 属性的值是否可以修改
[[Value]]:包含属性实际的值let person = {name: 'nnn'
}//name是属性,value等是name的特性,因为是数据//属性所以有value
//默认前三个为true,[[value]]设为制定的值
//name就是数据属性修改属性的默认属性,需要
Object.defineProperty()let person = {}
Object.defineProperty(person,'name',{writable: false,value: 'zhaosi'
});
console.log(person.name)
person.name = 'liunenng'
console.log(person.name)
输出
zhaosi
zhaosi

访问器属性

不包含数据值。包含一个获取(getter)和设置(setter)函数,但不是必需的
读取访问器属性,调用获取函数,写入访问器属性,调用设置函数。

[[Configurable]]:基本同上
[[Enumerable]]:同上
[[Get]]:获取函数
[[Set]]:设置函数
访问器属性是不能直接定义的,必须使用Object.defineProperty()let book = {year_: 2007,edition: 1
};
Object.defineProperty(book,'year',{get() {return this.year_;},set(v) {if(v > 2017) {this.year_= v;this.edition -= v;}}
})
book.year = 2018;
console.log(book.year)
console.log(book.year_)
console.log(book.edition)
输出
2018
2018
-2017year就起到了设置和获取year_的作用

定义多个属性
Object.defineProperties()

读取属性的特性

Object.getOwnPropertyDescriptor()
Object.getOwnPropertyDescriptors()

let book = {}
Object.defineProperties(book, {year_: {value: 2018,},edition: {value: 1},year: {get() {return this.year_},set(v) {this.year_ = v;}}
})
let des = Object.getOwnPropertyDescriptor(book,'year_')
console.log(des.value)
console.log(des.configurable)
console.log(typeof des.get)
let des1 = Object.getOwnPropertyDescriptor(book,'year')
console.log(typeof des.get)
console.log(Object.getOwnPropertyDescriptors(book))

合并对象

合并(merge)对象,就是把源对象所有的本地属性一起复制到目标对象上。有时候这种操作也被称为混入(mixin),因为目标对象通过混入源对象得到了增强

Object.assign()

对象标识及相等判定

Object.is()

console.log(Object.is(true,1))
console.log(Object.is({},{}))
console.log(Object.is(NaN,NaN))

增强的对象语法

//属性值简写
let name = 'matt'
let person = {name: name
}
let person = {name//变量名和属性名一样
}//可计算属性
const nameKey = 'name',ageKey = 'age',jobKey = 'job';
let person = {[nameKey]: 'matt',[ageKey]: 27,[jobKey]: 'software'
}//简写方法名
let person ={sayName: function(name) {console.log(name);}
}//old version
let person = {sayName(name) {console.log(name);}
}
person.sayName('mat')let person = {name_: '',get name() {return this.name_;},set name(name) {this.name_ = name;},sayName() {console.log(`my name is ${this.name_}`);}
}
person.name = 'matt'
person.sayName()
//简写方法名对获取函数 设置函数都是使用的
//简写方法名与可计算属性键相互兼容

对象解构

let person = {name: 'matt',age:21
}
let {name:personName, age: personAge} = person;
console.log(personName)
console.log(personAge)let {name, age, job = 'software'} = person;
console.log(name)
console.log(age)
console.log(job)
let a,b;
({name:a, age:b}=person)
console.log(a,b) //因为事先声明了ab所以要在()里面

创建对象

工厂模式

可以解决创建多个类似对象的问题,但没有解决对象标识问题(即新创建的对象是什么类型)

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 preson1 = createPerson('nnn',22,'aaa')
preson1.sayName()

构造函数模式

没有显式地创建对象
属性和方法直接赋值给了this
没有return
按照惯例,构造函数的首字母是要大写的,非构造函数首字母小写。

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('aa',22,'sss');
person1.sayName()这是力扣中JavaScript语言中树的建造
function TreeNode(val, left, right) {this.val = (val===undefined ? 0 : val)this.left = (left===undefined ? null : left)this.right = (right===undefined ? null : right)
}

创建Person的实例,应使用new操作符,会进行如下操作。
1.在内存中创建一个新对象
2.这个新对象内部的[ [ Prototype ]]特性被赋值为构造函数的prototype属性
3.构造函数内部的this被赋值为这个新对象
4.执行构造函数内部的代码
5.返回该对象

构造函数也可以使用函数表达式

构造函数也是函数

如果有new,就是构造函数
如果没有就是普通函数

function Person(name, age, job) {this.name = name;this.age = age;this.job = job;this.sayName = function () {console.log(this.name);}
}
let person = new Person();
Person('aa',22,'aaa')
sayName();let o = new Object()
Person.call(o,'aaa',222,'bbb');
o.sayName()

构造函数的问题

不同的person都要定义相同的sayName

原型模式

每个函数都会创建一个prototype属性,这个属性是一个对象,包含应该由特定引用类型的实例共享的属性和方法。实际上,这个对象就是通过调用构造函数创建的对象的原型。使用原型对象的好处是,在它上面定义的属性和方法可以被对象实例共享。原来在构造函数中直接赋值给对象实例的值,可以直接赋值给他们的原型

我理解:prototype这个属性是对象,你赋值给它,就会让所有对象实例都有赋值在上面的属性和方法。这个对象是构造函数创建的对象的原型,就是原材料、模板的感觉

function Person() {}
Person.prototype.name = 'zhaosi'
Person.prototype.age = 22;
Person.prototype.job = 'soft'
Person.prototype.sayName = function () {console.log(this.name)
}
let person1 = new Person();
person1.sayName()
理解原型

函数有prototype属性,指向原型对象
原型对象有constructor属性,指向构造函数
Person.prototype -> Person构造函数的原型对象
Person.prototype.constructor -> Person

实例对内部[ [Prototype ]]指针会赋值为构造函数的原型对象,但是不能直接访问。有些浏览器在对象(应该不只是实例,原型对象应该也有)上暴露__proto__属性,通过这个属性,可以访问实例对象的原型对象。

所以,实例对象与原型对象有直接关系,与构造函数并无直接关系

function Person() {}
console.log(typeof Person.prototype)//object,是一个对象
console.log(Person.prototype)
/*
{constructor: ƒ}在浏览器上看,这不全*/
console.log(typeof Person.prototype.constructor)//function
console.log(Person.prototype.constructor === Person)//true//正常的原型链都会终止于Object的原型对象,Object的原型对象是null
console.log(Person.prototype.__proto__ === Object.prototype)
//Person构(造函数).prototype(原型对象).proto(的原型对象),
//就是Object这个构造函数的原型对象
//__proto__并不推荐使用,具体看MDN Object.prototype.__proto__console.log(Person.prototype.__proto__.constructor === Object)
//Person这个构造函数的原型对象的原型对象的构造函数是Object
console.log(Person.prototype.__proto__.__proto__ === null)
//Person原型对象的原型对象(等于是Object的原型对象 )的原型对象是null
//Object的原型对象 的原型对象是null
console.log(Person.prototype.__proto__)

构造函数、原型对象和实例,是三个完全不同的对象

虽然不是所有实现都对外暴露了[ [Prototype ] ],但是可以使用isPrototypeOf(),会在传入参数的[ [ Prototype]]指向调用它的对象时返回true

function Person() {}
person1 = new Person()
console.log(Person.prototype.isPrototypeOf(person1)) //true
Object.getPrototypeOf(),返回参数的内部特性[ [ Prototype]] 的值。
function Person() {}
Person.prototype.name = 'liuneng'
person1 = new Person()
console.log(Object.getPrototypeOf(person1) == Person.prototype)
console.log(Object.getPrototypeOf(person1).name)Object.setPrototypeOf()向实例的私有特性[ [ Prototype]]写入一个新值。这样可以重写一个对象的原型继承关系
let biped = {numLegs:2
}
let person = {name:'aaa'
}
Object.setPrototypeOf(person,biped)
console.log(person.name)
console.log(person.numLegs)
console.log(Object.getPrototypeOf(person) === biped) //但不推荐使用这个方法Object.create()替代上面
let biped = {numLegs:2
}
let person = Object.create(biped)
person.name = 'aaa'
console.log(person.name)
console.log(person.numLegs)
console.log(Object.getPrototypeOf(person) === biped)

原型层级

就是使用一个属性,会从实例开始,如果有就返回,如果没有就找它的原型看有没有,知道找到,或者到了最后还没有

hasOwnProperty()实例对象是否有某个属性(原型上面的不算)

Object.getOwnProperty()只对实例属性有效。要取得原型对象的属性,必须直接在原型对象上调用该方法

for-in and in

function Person() {}
Person.prototype.name = 'nnn';
Person.prototype.age = 22;
Person.prototype.job = 'aaa'
Person.prototype.sayName = function () {console.log(this.name);
}let person1 = new Person()
let person2 = new Person()
console.log(person1.hasOwnProperty('name'))
console.log('name' in person1)person1.name = 'aaa'
console.log(person1.hasOwnProperty('name'))

in无论实例对象还是原型对象上,只要有就是true

function hasPrototypeProperty(object, name) {return !object.hasOwnProperty(name) && (name in object);
}
console.log(hasPrototypeProperty(person1,'name'))
可以判断是否只存在于原型对象

要想获得对象上所有可枚举的实例属性,可以使用
Object.keys(),返回包含该对象所有可枚举属性名称的字符串数组

let keys = Object.keys(Person.prototype);
console.log(keys)

只是自身的属性
Object.getOwnPropertyNames()
枚举 不枚举 都可以获得

Object.getOwnPropertySymbols()
针对符号为键的属性

let k1 = Symbol('k1')
let k2 = Symbol('k2')
let o = {[k1] : 'k1',[k2] : 'k2',name: 'aaa'
}
console.log(Object.getOwnPropertySymbols(o))let keys = Object.getOwnPropertyNames(Person.prototype)
console.log(keys)

属性枚举顺序

let k1 = Symbol('k1'),k2 = Symbol('k2');
let o = {1: 1,first: 'frist',[k1] : 'sym2',second: 'seconde',0: 0
}
o[k2] = 'sum2'
o[3] = 3;
o.third = 'third'
o[2] = 2;
for(const i in o) {console.log(i)
}

for-in Object.keys()枚举顺序不确定
Object.getOwnPropertyNames()
Object.getOwnPropertySymbols()
Object.assign()枚举顺序确定,先以升序枚举数值键、然后插入顺序枚举字符串和符号键。在对象字面量中定义的键以他们逗号分割的顺序插入

let k1 = Symbol('k1'),k2 = Symbol('k2'),k3 = Symbol('k3');
let o = {1: 1,first: 'frist',[k3]: 'hhh',[k1] : 'sym2',second: 'seconde',0: 0
}
o[k2] = 'sum2'
o[3] = 3;
o.third = 'third'
o[2] = 2;
console.log(Object.getOwnPropertyNames(o))
console.log(Object.getOwnPropertySymbols(o))输出
[ '0', '1', '2', '3', 'first', 'second', 'third' ]
[ Symbol(k3), Symbol(k1), Symbol(k2) ]

对象迭代

const o = {foo: 'bar',baz: 1,qux: {}
}
console.log(Object.values(o))
console.log(Object.entries(o))输出
[ 'bar', 1, {} ]
[ [ 'foo', 'bar' ], [ 'baz', 1 ], [ 'qux', {} ] ]

其他原型语法

为了避免每次创建一个实例对象,就需要在原型对象上添加相同的属性和方法,所以以如下方式解决

function Person() {}
Person.prototype = {name: 'aa',age: 22
}
Object.defineProperty(Person.prototype,'constructor',{enumerable: false,value: Person
})
//constructor 值改为Person,同时设置为不可枚举

原型的动态性

function Person() {}
let friend = new Person();
Person.prototype.sayHi = function () {console.log('hi')
}
friend.sayHi()这依然正确,因为friend有指向原型的指针,但如果重写了整个原型对象,friend指针不能指向新的原型对象,此时就是错误

原生对象原型

console.log(typeof Array.prototype.sort)
String.prototype.abc = function (text) {console.log(text)return 'success'
}
let msg = 'aaa'
console.log(msg.abc('i am cool'))输出
function
i am cool
success

原型的问题

共享性,你改了一个可能就全改了;
弱化构造函数传递初始化参数的能力

继承(最复杂的一部分来了,个人目前觉得)

感觉书上这部分讲的挺好,所以这部分笔记,打算主要采用费曼学习法,把相关内容讲一下,看能不能自己把这些东西讲明白了,别绕晕了。等以后再读这本书,可能读书笔记就以费曼学习法为主,自己大量讲解

继承有接口继承和实现继承,而es只支持实现继承,这主要通过原型链实现
原型链基本构想:一个原型是另一个类型的实例,则这个原型本身有一个内部指针指向另一个原型,另一个原型也有一个指针指向另一个构造函数,形成一个原型链

原型链

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())

首先构造函数supertype,和构造函数subtype,而super(后面简写了)原型对象上有getSuperValue这么一个方法。subtype的原型对象被赋值为supertype的实例对象,所以subtype.prototype就是supertype的一个实例,这个实例有一个指针,指向supertype.prototype,同时,这个supertype.prototype有一个constructor属性指向supertype这个构造函数。现在,subtype的原型对象上添加一个
getSubValue的方法。然后,instance为subtype实例对象,他有一个指针指向subtype的原型对象,然后这个原型对象如上面所说,一直向上指。instance调用getSuperValue方法,返回subproperty,这个方法需要他一直往上找,找到supertype.prototype这个原型对象才能找到,同时这个方法返回property,这个属性是实例属性,在SubType.prototype这个实例上(他是Supertype的实例)
哦对了,他们的constructor都是supertype

默认原型

所有引用类型都继承自Object。所以他们最后都能知道Object的原型对象,而toString()这些都在Object的原型对象上,所以自定义对象可以调用这些方法

原型与继承关系

instance
isPrototypeOf()
都可以判断继承关系

关于方法

原型链的问题

1.共享性
2.子类型在实例化时不能给父类型的构造函数传参

盗用构造函数(construct stealing 对象伪装 经典继承)

子类构造函数调用父类构造函数

function SuperType() {this.colors = ['a','b','c']
}
function SubType() {SuperType.call(this)
}
let ins1 = new SubType()
ins1.colors.push('d')
console.log(ins1.colors)let ins2 = new SubType()
console.log(ins2.colors)
解决了共享性问题

1.传递参数
可以在子类构造函数向父类构造函数传参

function SuperType(name) {this.name = name
}
function SubType() {SuperType.call(this,'nnn');this.age =22
}
let ins = new SubType()
console.log(ins.name)
console.log(ins.age)

2.问题
必须在构造函数中定义方法,因此函数不能重用
子类也不能访问父类原型上定义的方法

组合继承(伪经典继承)

使用原型链继承原型上的属性和方法,而通过盗用构造函数继承实例属性。让原型上的方法得以实现重用,每个实例又可以有自己的属性

function SuperType(name) {this.name = namethis.colors = ['a','b']
}
SuperType.prototype.sayName = function () {console.log(this.name)
}
function SubType(name,age) {SuperType.call(this,'nnn');this.age =age
}
SubType.prototype = new SuperType()
SubType.prototype.sayAge = function() {console.log(this.age);
}
let i1 = new SubType('aaa',22)
i1.colors.push('c')
console.log(i1.colors)
i1.sayAge()
i1.sayName()let i2 = new SubType('ggg',222)
console.log(i2.colors)
i2.sayAge()
i2.sayName()输出
[ 'a', 'b', 'c' ]
22
nnn
[ 'a', 'b' ]
222
nnn

首先,由上往下看,subtype.prototype 是一个supertype的实例,然后他的上面有个sayAge方法。同时Subtype又用了盗用构造函数这一模式。所以创建i1这个实例对象,会用构造函数subtype,而构造函数中先调用父类构造函数,为实例创建colors和name两个属性。同时supertype的原型对象上有一个sayName的方法,他会输出实例对象(i1)上面的name属性;然后完成supertype.call之后,会为实例对象
i1设置age属性,同时他是subtype的实例对象,他的原型对象上有sayAge方法,输出实例对象上的age。因为subtype的原型对象是supertype的实例对象,所以顺着原型链,subtype的原型对象内部有一个指针,指向supertype的原型对象,这个对象上有sayName,所以i1这个实例对象全都有,他得到了一切
i2同理

组合继承弥补了原型链和盗用构造函数不足,是使用最多的继承模式。也保留了instanceOf操作符和isPrototypeOf()方法识别合成对象的能力

原型式继承

不自定义类型也可以通过原型实现对象之间的信息共享

function object(o) {function F() {}F.prototype = o;return new F();
}
let person = {name: 'aaa',friends: ['zhangsan','lisi']
}
let anotherPerson = object(person)
anotherPerson.name = 'xiaoming'
anotherPerson.friends.push('wangwu')
console.log(person.friends)
console.log(person.name)输出
[ 'zhangsan', 'lisi', 'wangwu' ]
aaa

把一个对象o传进去,然后临时创建一个构造函数,这个构造函数的原型对象赋值为o,然后返回这个构造函数的实例对象,本质上实现了对o的浅复制

在下面的实际例子中。anotherPeroson就是返回的实例对象,所以他的原型对象赋值为o,有name,和friends,因为浅复制,所以改变他的friends就是改变person的friends

如此,实现了共享。这里实际上克隆了两个person

Object.create()将上面的思想规范化了

这种方法适合不需要单独创建构造函数,但仍然需要在对象间共享信息的场合。但要记住,属性中包含的引用值始终会在相关对象间共享,跟使用原型模式是一样的

寄生式继承

类似于寄生构造函数和工厂模式:创建一个实现继承的函数,以某种方式增强对象,然后返回这个对象

function createAnother(original) {let clone = object(original);clone.sayHi = function () {console.log('hi');}return clone;
}
function object(o) {function F() {}F.prototype = o;return new F();
}let person = {name:'aa',friends : ['a','b']
}
let anotherPerson = createAnother(person)
anotherPerson.sayHi()

先说上面抽象的createAnother中把original传入object,返回一个实例对象,赋值给clone,然后clone增强了sayHi方法,再返回clone这个对象
所以anotherPerson就是person的增强对象,具备sayHi方法

寄生式继承同样适合主要关注对象,不在乎类型和构造函数的场景。object不是寄生式继承必需的,任何返回新对象的函数都可以使用,上面的更多的是一种思想

寄生式组合继承

组合继承存在效率问题。主要的效率问题是父类构造函数始终会被调用两次:以此在创建子类原型是,另一次在子类构造函数中调用。本质上,子类原型对象是要包含超类对象的所有实例属性。子类构造函数只要在执行时重写自己的原型就行了。
不通过调用父类构造函数给子类原型赋值,而是取得父类原型的一个副本。

function inheritPrototype(subType,superType) {let prototype = object(superType.prototype);//创建对象prototype.constructor = subType;//增强对象,解决constructor丢失问题subType.prototype = prototype;//赋值对象
}

通过object,返回一个原型对象是父类原型对象的实例对象,赋值给prototype;然后解决constructor丢失问题;最后,子类原型对象赋值为prototype,即子类原型对象就是一个实例,其指向父类原型对象

虽然很绕,但道理确实和之前实现的是一样的

function inheritPrototype(subType,superType) {let prototype = object(superType.prototype);//创建对象prototype.constructor = subType;//增强对象,解决constructor丢失问题subType.prototype = prototype;//赋值对象
}
function SuperType(name) {this.name = name;this.colors = ['a','b'];
}
SuperType.prototype.sayName = function () {console.log(this.name);
}
function SubType(name, age) {SuperType.call(this,name);this.age =  age;
}
inheritPrototype(SubType,SuperType);
SubType.prototype.sayAge = function () {console.log(this.age);
}

只用一次superType,提高了效率。算是引用类型继承的最佳模式

class关键字具有正式定义类的能力,是基础性语法糖结构。实际上背后使用的仍然是原型和构造函数的概念

class Person{}类声明
const Animal = class {};类表达式
类定义不能提升类受块作用域限制

类包含:构造函数方法 、 实例方法 、 获取函数、 设置函数 、 静态类方法,但都不是必需的

类构造函数

constructor关键字用于在类定义块内部创建类的构造函数。方法名constructor会告诉解释器在使用new操作符创建类的新实例时,应该调用这个函数。构造函数的定义不是必需的,不定义默认为空函数
1.实例化
使用new 实例化Person等于使用new调用其构造函数

使用new调用类的构造函数会有如下操作:
在内存中创建一个新对象。
这个新对象内部[ [ prototype]]指针被赋值为构造函数的prototype属性。
构造函数内部的this被赋值为这个新对象(this指向新对象)
执行构造函数内部的代码
如果构造函数返回非空对象则返回;否则,返回刚创建的对象

默认分情况返回this对象;如果没有什么引用新创建的this对象,那么这个对象被销毁(我理解,就是返回来没人接着,就把返回的销毁了)。如果返回不是this对象,而是其他对象,那么这个对象不会通过instanceof操作符检测出和类相关联

class Person {constructor(override) {this.foo = 'foo';if(override) {return {bar: 'bar'}}}
}
let p1 = new Person()
let p2 = new Person(true)
console.log(p1)
console.log(p1 instanceof Person)
console.log(p2)
console.log(p2 instanceof Person)输出
Person { foo: 'foo' }
true
{ bar: 'bar' }
false

2.类是特设函数
es并没有正式的类这个类型。es类就是一种特殊函数
typeof返回function

类标签符有prototype属性,而这个原型也有一个constructor属性指向自身

class Person{}
console.log(Person.prototype)
console.log(Person === Person.prototype.constructor)

类是JavaScript中的一等公民(在编程语言中,一等公民可以作为函数参数,可以作为函数返回值,也可以赋值给变量。)。因此可以像其他对象或函数引用一样把类作为参数传递

let classList = [class {constructor(id) {this.id_ = id;console.log(`instace ${this.id_}`);}}
];
function createInstace(classDefinition, id) {return new classDefinition(id);
}
let foo = createInstace(classList[0],314);立即实例化
let p = new class Foo {constructor(x) {console.log(x);}
}('bar');
console.log(p)

实例、原型和类成员

类的语法可以非常方便的定义应该存在于实例上的成员、应该存在于原型上的成员,以及应该存在于类本身的成员

1.实例成员
每次通过new调用类标识符时,都会执行类构造函数。在这个函数内部,可以为新创建的实例(this)添加自由属性。至于添加什么样的属性,则没有限制。另外,在构造函数执行完毕后,仍然可以给实例继续添加新成员。
每个实例都对应一个唯一的成员对象,这意味所有成员不会在原型上共享
2.原型方法与访问器
为了在实例间共享方法,类定义语法把在类块中定义的方法称为原型方法

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

类定义也支持获取和设置访问器,与普通对象行为一样

class Person {set name(newName) {this.name_ = newName}get name() {return this.name_}
}
let p = new Person()
p.name = 'jake'
console.log(p.name)

3.静态类方法
可以在类上定义静态方法。这些方法通常用于执行不特定用于实例的操作,也不要求存在类的实例。与原型成员类似,每个雷伤只能有一个静态成员
静态类成员在类定义中使用static关键字作为前缀。在静态成员中,this引用类自身。其他同原型成员

class Person {constructor() {this.locate = () => console.log('instace ',this);}locate() {console.log('prototype',this)}static locate() {console.log('class',this)}
}
let p = new Person()
p.locate()
Person.prototype.locate()
Person.locate()

大概是实例上的成员用constructor,
原型上的成员用原型方法
类本身上的成员用静态方法

4.非函数原型和类成员

class Person {}
Person.greeting = 'hello'
Person.prototype.name = 'aaa'
let p = new Person()
在类定义外部添加成员数据

5.迭代器与生成器方法
支持在原型和类本身上定义生成器方法

class Person {*createNicknameIterator() {yield 'jack';yield 'jake';}static *createJobIterator() {yield 'butcher';yield 'baker';}
}
let jobIter = Person.createJobIterator();
console.log(jobIter.next().value)
console.log(jobIter.next().value)let p = new Person()
let nick = p.createNicknameIterator()
console.log(nick.next().value)
console.log(nick.next().value)let h = Person.prototype.createNicknameIterator();
console.log(h.next().value)
console.log(h.next().value)

因此,可以通过添加一个默认的迭代器,把类实例变为可迭代对象

class Person {constructor() {this.nicknames = ['jek','jjj','hhhhhh'];}*[Symbol.iterator]() {yield *this.nicknames.entries();}
}
let p = new Person()
for(let [idx, nickname] of p) {console.log(nickname)
}

继承

类继承机制。背后依旧是原型链
1.继承基础
extends关键字,就可以继承任何拥有[ [ Construct]]和原型的对象。这意味不仅可以继承一个类,也可以继承普通的构造函数(保持向后兼容)

2.构造函数、HomeObject、super()
派生类的方法可以通过super关键字引用他们的原型。这个关键字只嗯呢该在派生类中使用,仅限于类构造函数、实例方法、静态方法内部。在类构造函数用super可以调用父类构造函数

class Vehicle {constructor() {this.hasEngine = true;}
}
class Bus extends Vehicle {constructor() {//super前不用this,否则出错super();//super.constructor()console.log(this instanceof Vehicle)console.log(this)}
}
new Bus()

es6给类构造函数和静态方法添加了内部特性[ [ HomeObject]],这个特性是一个指针,指向定义该方法的对象。这个指针是自动复制的,而且只能在js引擎内部访问,super始终会定义为[ [ HomeObject]]的原型

super只在派生类构造函数和静态方法使用
不能单独使用super,要么用它调用构造函数或者静态方法
super()会调用父类构造函数,并将返回的实例赋值给this
没有定义类构造函数,在实例化派生类时会调用super()

3.抽象基类
供其他类继承,但本身不被实例化。new.target可以实现,其保存通过new关键字调用的类或函数。通过在实例化时检测new.target是不是抽象基类,可以阻止对其实例化

class Vehicle {constructor() {console.log(new.target);if(new.target === Vehicle) {throw new Error('Vehicle') ;}}
}
class Bus extends Vehicle{}
new Bus()
new Vehicle()

也可以通过抽象基类要求派生类必须定义某个方法

4.继承内置类型
可以方便扩展内置类型

class superArray extends Array {}

5.类混入
把不同类的行为集中到一个类是一种常见的js模式。虽然es6没有显示支持多类继承,但通过现有特性可以轻松模拟这种行为。

混入模式可以通过在一个表达式中连缀多个混入元素实现,这个表达式最终会解析为一个可以被继承的类。

class Vehicle {}
let FooM = (SuperC) => class extends SuperC {foo() {console.log('aaa')}
}
let  BsrM = (SuperC) => class extends SuperC {boo() {console.log('bbb')}
}
let Caa = (SuperC) => class extends SuperC {coo() {console.log('ccc')}
}
class Bus extends FooM(BsrM(Caa(Vehicle))) {}
let b = new Bus()
b.foo()
b.boo()
b.coo()
class Vehicle {}
let FooM = (SuperC) => class extends SuperC {foo() {console.log('aaa')}
}
let  BsrM = (SuperC) => class extends SuperC {boo() {console.log('bbb')}
}
let Caa = (SuperC) => class extends SuperC {coo() {console.log('ccc')}
}
function mix(BaseClass,...Mixins) {return Mixins.reduce((acc,cur) => cur(acc), BaseClass)
}
class Bus extends  mix(Vehicle,FooM,BsrM,Caa) {}
let b = new Bus()
b.foo()
b.boo()

最后,composition over inheritance 复合胜过继承,react中会有体现,他抛弃混入模式,转向了复合模式

红宝书读书笔记 第八章相关推荐

  1. JS红宝书·读书笔记

    JavaScript高级程序设计 花了半个多月的时间,终于又把"JS红宝书"又撸了一遍. 第一次读"JS红宝书"还是2015年初学JS的时候,那时候只是把语法部 ...

  2. 红宝书读书笔记 第五章

    基本引用类型 Date https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Date MD ...

  3. 红宝书读书笔记 第九章

    代理与反射 代理感觉就是外围保护的一种感觉,我先对操作进行筛选处理,然后再传给对象 代理基础 创建代理 代理用构造函数创建,接受两个参数,目标对象和处理程序对象 const target = {id: ...

  4. 红宝书背诵笔记 — 基础词

    红宝书基础词 基础词-Unit1 基础词-Unit2 基础词-Unit3 基础词-Unit4 基础词-Unit5 基础词-Unit6 基础词-Unit7 基础词-Unit8 基础词-Unit9 基础词 ...

  5. 红宝书背诵笔记 — 简单基础词语

    简单基础词 a开头的简单基础词 b开头的简单基础词 c开头的简单基础词 d开头的简单基础词 e开头的简单基础词 f开头的简单基础词 g开头的简单基础词 h开头的简单基础词 i开头的简单基础词 j开头的 ...

  6. 《JavaScript高级程序设计(第四版)》红宝书学习笔记(2)(第四章:变量、作用域与内存)

    个人对第四版红宝书的学习笔记.不适合小白阅读.这是part2.持续更新,其他章节笔记看我主页. (记 * 的表示是ES6新增的知识点,记 ` 表示包含新知识点) 第四章:变量.作用域与内存 4.1 原 ...

  7. 《JavaScript高级程序设计(第四版)》红宝书学习笔记(1)

    个人对第四版红宝书的学习笔记.不适合小白阅读.这是part1,包含原书第二章(HTML中的Javascript)和第三章(语言基础).持续更新,其他章节笔记看我主页. (记 * 的表示是ES6新增的知 ...

  8. 红宝书阅读笔记——OPENGL渲染管线

    之前读的时候一直觉得红宝书是很艰涩难懂的,不如NEHE的教程简单. 后来才发觉是自己没基础,几番折腾之后也只能用OPENGL做些简单的东西.半年没写,连glBegin都给忘了. 图形学的大作业要求写个 ...

  9. 红宝书阅读笔记(持续更新)

    今天是2021年8月15日 ,在自学的同时开始阅读前端相关的书籍,愿不断在学习中进步. 这一本是<JavaScript高级程序设计>(第四版),也俗称红宝书,第四版中加入了ES6. 下一本 ...

最新文章

  1. python全栈慕课网靠谱么_全栈和python的区别 ?
  2. python输出一个月日历表_关于python一个月总结
  3. iOS 学习 - 13.微信分享链接、QQ 分享图片
  4. es的聚合mysql聚合速度_Elasticsearch(8) --- 聚合查询(Metric聚合)
  5. python 类和对象 atm_Python 类和对象
  6. 服务器运行jupyter notebook,解决办法
  7. php如何测量坐标周围,php – 如何检查经度/纬度点是否在坐标范围内?
  8. 【收集】C#一些基础的面试题
  9. Can‘t connect to MySQL server on ‘localhost‘(10061)【SQLyog】
  10. 浅谈深拷贝 与 浅拷贝
  11. 安卓案例:注册用户免启动时的广告页面
  12. RN style使用以及常用样式总结
  13. CIO关注:数字化转型带来的IT版图巨变正在成为新的挑战
  14. 2018-03-19Flask框架网站开发
  15. 移动html5 滑动 zepto,移动端使用zepto编写的滑动事件
  16. dnf脚本色块补丁_DNF:史上最难地图,没有小怪只有BOSS,做成团本连飞行员都得坠机...
  17. Unity接入Steam成就
  18. 中华通史.上古史-夏
  19. 【博学谷学习记录】超强总结,用心分享 | JavaString类知识要点总结
  20. minigui学习笔记四

热门文章

  1. 快速的绘制一幅可爱的柯基犬插图教程
  2. 安利 3 个 pandas 数据探索分析神器!
  3. linux man 中文 mac,技术|MAC 系统中显示中文MAN手册
  4. jvm G1 深度分析
  5. linux服务器清除cdn,Linux服务器中查找并删除大文件的五种方法,Linux系统清除文件内容的命令分享...
  6. linux安装trac+svn+apache+wike,搭建apache+svn+trac平台
  7. ios 模拟器如何模拟多点触控
  8. [转载][转]无线衰落信道、多径与OFDM、均衡技术
  9. docker私服配置
  10. 百度开放平台的第三方开发步骤详解