红宝书读书笔记 第八章
對象、類與面向對象編程
屬性的類型
內部屬性用兩個中括號如[ [ 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)函數,但不是必需的
讀取訪問器屬性,調用獲取函數,寫入訪問器屬性,調用設置函數。
定義多個屬性
Object.defineProperties()
讀取屬性的特性
Object.getOwnPropertyDescriptor()
Object.getOwnPropertyDescriptors()
合并對象
合并(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
按照慣例,構造函數的首字母是要大寫的,非構造函數首字母小寫。
創建Person的實例,應使用new操作符,會進行如下操作。
1.在內存中創建一個新對象
2.這個新對象內部的[ [ Prototype ]]特性被賦值為構造函數的prototype屬性
3.構造函數內部的this被賦值為這個新對象
4.執行構造函數內部的代碼
5.返回該對象
構造函數也可以使用函數表達式
構造函數也是函數
如果有new,就是構造函數
如果沒有就是普通函數
構造函數的問題
不同的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(),返回包含該對象所有可枚舉屬性名稱的字符串數組
只是自身的屬性
Object.getOwnPropertyNames()
枚舉 不枚舉 都可以獲得
Object.getOwnPropertySymbols()
針對符號為鍵的屬性
屬性枚舉順序
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()枚舉順序確定,先以升序枚舉數值鍵、然后插入順序枚舉字符串和符號鍵。在對象字面量中定義的鍵以他們逗號分割的順序插入
對象迭代
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.傳遞參數
可以在子類構造函數向父類構造函數傳參
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不是寄生式繼承必需的,任何返回新對象的函數都可以使用,上面的更多的是一種思想
寄生式組合繼承
組合繼承存在效率問題。主要的效率問題是父類構造函數始終會被調用兩次:以此在創建子類原型是,另一次在子類構造函數中調用。本質上,子類原型對象是要包含超類對象的所有實例屬性。子類構造函數只要在執行時重寫自己的原型就行了。
不通過調用父類構造函數給子類原型賦值,而是取得父類原型的一個副本。
通過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' } false2.類是特設函數
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 {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引用類自身。其他同原型成員
大概是實例上的成員用constructor,
原型上的成員用原型方法
類本身上的成員用靜態方法
4.非函數原型和類成員
class Person {} Person.greeting = 'hello' Person.prototype.name = 'aaa' let p = new Person() 在類定義外部添加成員數據5.迭代器與生成器方法
支持在原型和類本身上定義生成器方法
因此,可以通過添加一個默認的迭代器,把類實例變為可迭代對象
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可以調用父類構造函數
es6給類構造函數和靜態方法添加了內部特性[ [ HomeObject]],這個特性是一個指針,指向定義該方法的對象。這個指針是自動復制的,而且只能在js引擎內部訪問,super始終會定義為[ [ HomeObject]]的原型
super只在派生類構造函數和靜態方法使用
不能單獨使用super,要么用它調用構造函數或者靜態方法
super()會調用父類構造函數,并將返回的實例賦值給this
沒有定義類構造函數,在實例化派生類時會調用super()
3.抽象基類
供其他類繼承,但本身不被實例化。new.target可以實現,其保存通過new關鍵字調用的類或函數。通過在實例化時檢測new.target是不是抽象基類,可以阻止對其實例化
也可以通過抽象基類要求派生類必須定義某個方法
4.繼承內置類型
可以方便擴展內置類型
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中會有體現,他拋棄混入模式,轉向了復合模式
總結
以上是生活随笔為你收集整理的红宝书读书笔记 第八章的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: CSU 1224 ACM小组的古怪象棋
- 下一篇: 开发你自己的Android 授权管理器