粗略記錄一下,歡迎各位大佬糾正(ORZ)html
2019.9.5 修改了對象有constructor 屬性的錯誤,正確的是對象的constructor 屬性是來自構造函數的原型對象的(fn.prototype.constructor )設計模式
咱們知道,JS基礎數據類型是 number,string,boolean,undefined和null,而引用類型就object,
以前看的時候,我很奇怪爲何像var str = "";這個str明明的類型是String,爲何它卻能夠引用String.prototype原型對象的屬性和方法呢,而且它確實有對象纔有的__proto__數組
var str = ''; console.log(str.__proto__ === String.prototype) //true console.log(str.constructor === String) //true console.log(str instanceof String); //false,前面兩個都符合了,這個居然是返回false,不是String的實例 console.log(String.prototype.isPrototypeOf(str)); //false,跟instanceof功能是同樣的
後面百度查了一下,緣由是閉包
在讀取字符串的時候會建立一個對象,可是這個對象只是臨時的,因此咱們稱它爲臨時對象,學術名字叫包裝對象,說它臨時,是由於咱們在讀取它的屬性的時候,js會把這個string字符串經過new String()方式建立一個字符串對象,一旦引用結束,這個對象就被銷燬了。函數
因此說就是像讀取對象那樣讀取屬性的時候,暗地裏幫我new String()了,不讀取的時候,就是基礎類型,因此判斷是否是實例才返回了falsepost
str.name = 'nihao'; //能夠這樣寫不報錯,由於暗地裏幫我對象化了, str.name //能夠點name出來,可是是undefined,沒錯
總結:目前發現除了undefined和null不能這樣搞,其餘類型都是有__proto__,因此說,JS幾乎全部值都爲對象this
首先,要明確兩點的是spa
好了,那明確這兩點以後,再說說這三者有什麼關係,先放圖吧.net
先說一下這個,按照這個圖的意思,有一個構造Person函數,這個函數默認就會有prototype屬性,這個屬性指向的值是一個對象,咱們叫作原型對象,而後呢,Person.prototype這個原型對象,它也會有一個constructor屬性,這個屬性默認指回構造函數,也就是Person函數,原型對象的name,age這些就是咱們本身往這個對象加的,Person.prototype.name = 'xxx',就像這樣,而後
兩個實例,person1,person2這兩個對象,有__proto__屬性對吧,它指向是構造函數的原型對象,就是Person.prototypeprototype
//構造函數Person function Person(){} //往原型對象加值 Person.prototype.name = 'mychirs'; Person.prototype.age = 29; Person.prototype.job = 'Software Engineer'; Person.prototype.sayName = function(){ alert(this.name); }; //兩個實例 var person1 = new Person(); var person2 = new Person(); console.log(Person.prototype.constructor === Person) //true console.log(person1.constructor === Person.prototype.constructor) //true console.log(person1.__proto__ === Person.prototype) //true console.log(person2.__proto__ === Person.prototype) //true
好了,說完上面那個圖已經差很少了,再放多一張圖
這個圖其實補充了兩點,
第一,原型鏈的盡頭是Object.prototype.__proto__,值爲null;
第二,Function.constructor這個值,正常來講,應該是指向實例Function這個函數的更上一個構造函數的原型對象的constructor,可是這張圖已經沒了,由於Function這個已是最高的構造函數了,Function.constructor仍是Function.prototype.constructor
//構造函數Person function Person(){} var obj = {} var person1 = new Person() console.log(Person.constructor.constructor.constructor === Function) //true,一直點下去都是這樣 console.log(obj.constructor === Object.prototype.constructor ) //true console.log(obj.constructor.prototype.__proto__=== null) //true,原型鏈盡頭,null
JavaScript 語言的繼承不經過 class(ES6 引入了class 語法),而是經過「原型對象」(prototype)實現,通常來講,
若是屬性和方法在實例裏找不到的話,會經過實例,也就是對象的__proto__,屬性,找到構造函數的原型對象(fn.prototype),在這裏面找屬性和方法,若是再找不到的的話,原型對象也是對象是吧,因此它就會經過fn.prototype.__proto__,找到對象構造函數的原型對象,也就是(Object.prototype)這裏找,若是再找不到,就到盡頭拉,由於Object.prototype.__proto__會返回null了。
下面介紹幾種常見繼承方式。
//父類型 function Person(name, age) { this.name = name, this.age = age, this.play = [1, 2, 3] this.setName = function () { } } Person.prototype.setAge = function () { } //子類型 function Student(price) { this.price = price this.setScore = function () { } } Student.prototype = new Person() // 子類型的原型爲父類型的一個實例對象 var s1 = new Student(15000) var s2 = new Student(14000) console.log(s1,s2)
分析以前,先大概說這個new 關鍵字在實例對象的時候作了什麼操做,其實就三步。
var obj = {}; obj.__proto__ = F.prototype; F.call(obj);
基於上面介紹,那咱們如今就重點看看 Student.prototype = new Person() 這句代碼就好了,能夠分爲兩步:
{ name:undefined, age:undefined, play:[1, 2, 3], setName:function () { }, __proto__:Person.prototype }
2.而後這個值賦給了Student.prototype, 後面,當咱們訪問Student的實例的時候,它先會在自身屬性找對應屬性和方法,找不到就會去Student.prototype這裏找,由於咱們Student.prototype已經賦值爲new Person,因此當找不到的話,會再沿着Student.prototype.__proto__指向的Person.prototype上面找
這裏有一個知識點要補充一下,注意!原型對象上面的引用數據類型會共享,基礎數據類型不會
var s1 = new Student() var s2 = new Student() s1.play.push(4) console.log(s1.play) //[1, 2, 3, 4] console.log(s2.play) //[1, 2, 3, 4]
總結:
要點:子類的原型賦值爲父類的一個實例對象。
缺點:
1.父類的屬性和方法都往子類的原型對象上面加,若是這時候父類屬性有引用數據類型的話將會共享
2.建立子類實例時,沒法向父類構造函數傳參
function Person(name, age) { this.name = name, this.age = age, this.play = [1,2,3] this.setName = function () {} } Person.prototype.setAge = function () {} function Student(name, age, price) { Person.call(this, name, age) // 至關於: this.Person(name, age) this.price = price }
這種雖然能夠傳參了,引用類型也不會相互影響了,可是咱們也很明顯的發現,它其實只把Person裏面的this.name那些所有搬到了Student裏面而已,這裏還會有一個問題,就是函數沒有複用,每個對象裏面都會再寫一次函數,儘管代碼是如出一轍的。最後就是沒有動過原型鏈,因此Person.prototype的屬性和方法是一個也拿不到的
var s1 = new Student('Tom', 20, 15000) var s2 = new Student('Tom', 20, 15000) s1.play.push(4) s1.play //[1,2,3,4] s2.play //[1,2,3] s1.setAge //undefined
總結:
要點:在子類構造函數中通用call()調用父類構造函數。
缺點:
1.只能繼承父類的實例屬性和方法,不能繼承原型屬性和方法
function Person(name, age) { this.name = name this.age = age this.play = [1,2,3] this.setAge = function () {console.log('我是person類實例函數') } } Person.prototype.plays = [9,9,9] Person.prototype.setAges = function () { console.log("我是person原型對象的函數") } function Student(name, age, price) { Person.call(this,name,age) this.price = price this.setScore = function () { } } Student.prototype = new Person() //Student.prototype.constructor = Student//組合繼承也是須要修復構造函數指向的 Student.prototype.sayHello = function () { }
從代碼能夠看出,在子類用了call,而後也把子類的原型對象賦值爲父類的實例,把上面兩個結合在一塊兒用而已,咱們再看看上面代碼,我故意在Person的構造函數加了一個setAge的方法,在Person原型對象加了一個數組,其實我想說的是,這種方法是能夠解決上面那兩個方式的問題,可是這個前提是在你遵循必定的規範,好比不要在構造函數加方法,不要在原型對象加引用類型的數據,否則同樣仍是有問題
var s1 = new Student('Tom', 20, 15000) var s2 = new Student('Tom', 20, 15000) //在構造函數裏面的不會有影響 s1.play.push(4) s1.play //[1,2,3,4] s2.play //[1,2,3] //原型對象上面的會共享 s1.plays //[9,9,9] s1.plays.push(9) s2.plays //[9, 9, 9, 9] s1.setAge == s2.setAge //false,在構造函數裏方法沒有複用 s1.setAges == s2.setAges //true,原型對象上面的方法就是複用
總結:
要點:在子類構造函數中通用call()調用父類型構造函數。而後又把子類的原型對象賦值爲父類的實例
缺點:
1.調用了兩次構造函數
上面咱們說到,構造加原型鏈繼承的組合繼承會執行兩次new操做,下面這個方式就是爲了解決這個調用兩次的缺點所誕生的,也算目前最合適的方案。
function Person(name, age) { this.name = name this.age = age this.play = [1,2,3] this.setAge = function () {console.log('我是person類實例函數') } } Person.prototype.names= '父類原型名字' Person.prototype.plays = [9,9,9] Person.prototype.setAges = function () { console.log("我是person原型對象的函數") } function Student(name, age, price) { Person.call(this,name,age) this.price = price this.setScore = function () { } } Student.prototype = Object.create(Person.prototype) //就是這裏不同 //Student.prototype.constructor = Student//組合繼承也是須要修復構造函數指向的 Student.prototype.sayHello = function () { }
咱們看看代碼,這個方法惟一的不一樣就是把Student.prototype = new Person()換成了Student.prototype = Object.create(Person.prototype)我先大概說一下Object.create(),
object.create() 接收兩個參數:
//這個對象用來作原型對象 var person = { name: '我是原型name', plays:[1,2,3] } var s1 = Object.create(person) s1 //{}空對象 s1.__proto__ === person //true,原型對象此時就是person s1.name //'我是原型name' 自身屬性沒有,拿原型對象裏面的 var s2 = Object.create(person,{ name:{ value: '我本身的name' } }) s2 //{name: "我本身的name"} s2.__proto__ === person //true,原型對象此時就是person s2.name //'我本身的name' 自身屬性就有 s1.plays.push(4) s1.plays //[1, 2, 3, 4],原型對象的引用數據類型是會共享的 s2.plays //[1, 2, 3, 4],原型對象的引用數據類型是會共享的
介紹完這個以後,咱們就能夠回頭看看這個語句Student.prototype = Object.create(Person.prototype),
這句話,把咱們的Student的原型對象的.__proto__ 指向了Person的原型對象,這樣,當咱們訪問Student的實例,好比s1.xxx,它會訪問自身,若是沒有,這時候s1.__proto__ 指向Student.prototype,若是Student.prototype又沒有,這時Student.prototype.__proto__ 指向Person.prototype,因此就會去到Person.prototype上面找。
咱們知道Student.prototype = new Person(),這句話其實跑了兩個做用,第一個做用跟Object.create同樣,調整了__proto__ 的指向,第二個做用,其實它也把Person構造函數的this.name這些也往Student.prototype這上面加了,只是咱們在訪問實例屬性的時候,因爲實例裏面已經有,(用了call嘛)因此纔不會讀到原型對象上面的,因此這也是這個方案的優點。
總結:
要點:用Object.create(),控制子類的原型對象的__proto__ 指向父類的原型對象
缺點:
1.暫無
我只寫幾個常見的,由於我百度了一下好像有好多種 = =
這個模式就是保證一個類只有一個實例,實現的方法通常是先判斷實例存在與否,若是存在直接返回,若是不存在就建立了再返回,這就確保了一個類只有一個實例對象。(我們平時用的window就是一個單例)
function Person(name,age){ this.name = name; this.age= age; } var getPerson = (function(){ let ins = null; return function(name,age){ if(!ins){ ins = new Person(name,age) } return ins; } })()
注意看看getPerson 這個函數就好了,因爲函數裏面又返回了一個函數,因此造成了一個閉包,ins 這個變量不會被銷燬。
var p1 = new getPerson('你好',100) var p2 = new getPerson('好你',99) p1 //{name: "你好", age: 100} p2 //{name: "你好", age: 100} p1 === p2 //true
由於p1實例化的時候,ins變量已經有值,因此當p2也實例化的時候,getPerson只會直接返回p1那個實例,不會進行第二次new操做,因此這兩個是相等的
工廠模式是指提供一個建立對象的接口而不保留具體的建立邏輯,能夠根據輸入類型建立對象。讓子類自行決定實例化哪種工廠類,實際的建立對象過程在子類中進行。咱們下面上代碼解釋一下
let UserFactory = function (role) { function SuperAdmin() { this.name = "超級管理員", this.viewPage = ['首頁', '通信錄', '發現頁', '應用數據', '權限管理'] } function Admin() { this.name = "管理員", this.viewPage = ['首頁', '通信錄', '發現頁', '應用數據'] } function NormalUser() { this.name = '普通用戶', this.viewPage = ['首頁', '通信錄', '發現頁'] } switch (role) { case 'superAdmin': return new SuperAdmin(); break; case 'admin': return new Admin(); break; case 'user': return new NormalUser(); break; default: throw new Error('參數錯誤, 可選參數:superAdmin、admin、user'); } } //調用 let superAdmin = UserFactory('superAdmin'); let admin = UserFactory('admin') let normalUser = UserFactory('user')
UserFactory就是一個簡單工廠,在該函數中有3個構造函數分別對應不一樣的權限的用戶。當咱們調用工廠函數時,只須要傳遞superAdmin, admin, user這三個可選參數中的一個獲取對應的實例對象
暫告一段落
參考連接:
挺好的原型對象說明文章
原型鏈說明文章
JavaScript常見的六種繼承方式
JS原型鏈與繼承別再被問倒了
JavaScript 單例模式
從ES6從新認識JavaScript設計模式(二): 工廠模式