<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          所有JS原型相關(guān)知識(shí)點(diǎn)

          共 23754字,需瀏覽 48分鐘

           ·

          2021-03-20 12:36

          前言

          The last time, I have learned

          【THE LAST TIME】一直是我想寫(xiě)的一個(gè)系列,旨在厚積薄發(fā),重溫前端。

          也是給自己的查缺補(bǔ)漏和技術(shù)分享。

          歡迎大家多多評(píng)論指點(diǎn)吐槽。

          系列文章均首發(fā)于公眾號(hào)【全棧前端精選】,筆者文章集合詳見(jiàn)GitHub 地址:Nealyang/personalBlog。目錄和發(fā)文順序皆為暫定

          首先我想說(shuō),【THE LAST TIME】系列的的內(nèi)容,向來(lái)都是包括但不限于標(biāo)題的范圍。

          再回來(lái)說(shuō)原型,老生常談的問(wèn)題了。但是著實(shí) 現(xiàn)在不少熟練工也貌似沒(méi)有梳理清楚 FunctionObject、prototype__proto__的關(guān)系,本文將從原型到繼承到 es6 語(yǔ)法糖的實(shí)現(xiàn)來(lái)介紹系統(tǒng)性的介紹 JavaScript 繼承。如果你能夠回答上來(lái)以下問(wèn)題,那么這位看官,基本這篇不用再花時(shí)間閱讀了~

          • 為什么 typeof 判斷 nullObject 類(lèi)型?
          • FunctionObject 是什么關(guān)系?
          • new 關(guān)鍵字具體做了什么?手寫(xiě)實(shí)現(xiàn)。
          • prototype__proto__是什么關(guān)系?什么情況下相等?
          • ES5 實(shí)現(xiàn)繼承有幾種方式,優(yōu)缺點(diǎn)是啥
          • ES6 如何實(shí)現(xiàn)一個(gè)類(lèi)
          • ES6 extends 關(guān)鍵字實(shí)現(xiàn)原理是什么

          如果對(duì)以上問(wèn)題有那么一些疑惑~那么。。。

          THE LAST TIME 系列回顧

          目錄

          雖文章較長(zhǎng),但較為基礎(chǔ)。大家酌情閱讀所需章節(jié)。

          注意文末有思考題哦~~

          • 原型一把梭
            • 函數(shù)對(duì)象和普通對(duì)象
            • __proto__
            • prototype
            • constructor
          • typeof && instanceof 原理淺析
            • typeof 基本用法
            • typeof 原理淺析
            • instanceof 基本用法
            • instanceof 原理淺析
          • ES5 中的繼承實(shí)現(xiàn)方式
            • new 手寫(xiě)版本一
            • new 手寫(xiě)版本二
            • new 關(guān)鍵字
            • 類(lèi)式繼承
            • 構(gòu)造函數(shù)繼承
            • 組合式繼承
            • 原型式繼承
            • 寄生式繼承
            • 寄生組合式繼承
          • ES6 類(lèi)的實(shí)現(xiàn)原理
            • _inherits
            • _possibleConstructorReturn
            • 基礎(chǔ)類(lèi)
            • 添加屬性
            • 添加方法
            • extend 關(guān)鍵字

          原型一把梭

          這。。。說(shuō)是最基礎(chǔ)沒(méi)人反駁吧,說(shuō)沒(méi)有用有人反駁吧,說(shuō)很多人到現(xiàn)在沒(méi)梳理清楚沒(méi)人反駁吧!OK~ 為什么文章那么多,你卻還沒(méi)有弄明白?

          在概念梳理之前,我們還是放一張老掉牙所謂的經(jīng)典神圖:

          • function Foo 就是一個(gè)方法,比如JavaScript 中內(nèi)置的 ArrayString
          • function Object 就是一個(gè) Object
          • function Function 就是 Function
          • 以上都是 function,所以 __proto__都是Function.prototype
          • 再次強(qiáng)調(diào),String、Array、Number、Function、Object都是 function

          老鐵,如果對(duì)這張圖已非常清晰,那么可直接跳過(guò)此章節(jié)

          老規(guī)矩,我們直接來(lái)梳理概念。

          函數(shù)對(duì)象和普通對(duì)象

          老話(huà)說(shuō),萬(wàn)物皆對(duì)象。而我們都知道在 JavaScript 中,創(chuàng)建對(duì)象有好幾種方式,比如對(duì)象字面量,或者直接通過(guò)構(gòu)造函數(shù) new 一個(gè)對(duì)象出來(lái):

          暫且我們先不管上面的代碼有什么意義。至少,我們能看出,都是對(duì)象,卻存在著差異性

          其實(shí)在 JavaScript 中,我們將對(duì)象分為函數(shù)對(duì)象和普通對(duì)象。所謂的函數(shù)對(duì)象,其實(shí)就是 JavaScript 的用函數(shù)來(lái)模擬的類(lèi)實(shí)現(xiàn)。JavaScript 中的 Object 和 Function 就是典型的函數(shù)對(duì)象。

          關(guān)于函數(shù)對(duì)象和普通對(duì)象,最直觀(guān)的感受就是。。。咱直接看代碼:

          function fun1(){};
          const fun2 = function(){};
          const fun3 = new Function('name','console.log(name)');

          const obj1 = {};
          const obj2 = new Object();
          const obj3 = new fun1();
          const obj4 = new new Function();


          console.log(typeof Object);//function
          console.log(typeof Function);//function
          console.log(typeof fun1);//function
          console.log(typeof fun2);//function
          console.log(typeof fun3);//function
          console.log(typeof obj1);//object
          console.log(typeof obj2);//object
          console.log(typeof obj3);//object
          console.log(typeof obj4);//object

          不知道大家看到上述代碼有沒(méi)有一些疑惑的地方~別著急,我們一點(diǎn)一點(diǎn)梳理。

          上述代碼中,obj1,obj2,obj3obj4都是普通對(duì)象,fun1,fun2fun3 都是 Function 的實(shí)例,也就是函數(shù)對(duì)象。

          所以可以看出,所有 Function 的實(shí)例都是函數(shù)對(duì)象,其他的均為普通對(duì)象,其中包括 Function 實(shí)例的實(shí)例。

          JavaScript 中萬(wàn)物皆對(duì)象,而對(duì)象皆出自構(gòu)造(構(gòu)造函數(shù))。

          上圖中,你疑惑的點(diǎn)是不是 Functionnew Function 的關(guān)系。其實(shí)是這樣子的:

          Function.__proto__ === Function.prototype//true

          __proto__

          首先我們需要明確兩點(diǎn):1??__proto__constructor對(duì)象獨(dú)有的。2??prototype屬性是函數(shù)獨(dú)有的;

          但是在 JavaScript 中,函數(shù)也是對(duì)象,所以函數(shù)也擁有__proto__constructor屬性。

          結(jié)合上面我們介紹的 ObjectFunction 的關(guān)系,看一下代碼和關(guān)系圖

          function Person(){…};
          let nealyang = new Person();
          proto

          再梳理上圖關(guān)系之前,我們?cè)賮?lái)講解下__proto__。

          __proto__ 的例子,說(shuō)起來(lái)比較復(fù)雜,可以說(shuō)是一個(gè)歷史問(wèn)題。

          ECMAScript 規(guī)范描述 prototype 是一個(gè)隱式引用,但之前的一些瀏覽器,已經(jīng)私自實(shí)現(xiàn)了 __proto__這個(gè)屬性,使得可以通過(guò) obj.__proto__ 這個(gè)顯式的屬性訪(fǎng)問(wèn),訪(fǎng)問(wèn)到被定義為隱式屬性的 prototype。

          因此,情況是這樣的,ECMAScript 規(guī)范說(shuō) prototype 應(yīng)當(dāng)是一個(gè)隱式引用:

          • 通過(guò) Object.getPrototypeOf(obj) 間接訪(fǎng)問(wèn)指定對(duì)象的 prototype 對(duì)象
          • 通過(guò) Object.setPrototypeOf(obj, anotherObj) 間接設(shè)置指定對(duì)象的 prototype 對(duì)象
          • 部分瀏覽器提前開(kāi)了 __proto__ 的口子,使得可以通過(guò) obj.__proto__ 直接訪(fǎng)問(wèn)原型,通過(guò) obj.__proto__ = anotherObj 直接設(shè)置原型
          • ECMAScript 2015 規(guī)范只好向事實(shí)低頭,將 __proto__ 屬性納入了規(guī)范的一部分

          從瀏覽器的打印結(jié)果我們可以看出,上圖對(duì)象 a 存在一個(gè)__proto__屬性。而事實(shí)上,他只是開(kāi)發(fā)者工具方便開(kāi)發(fā)者查看原型的故意渲染出來(lái)的一個(gè)虛擬節(jié)點(diǎn)。雖然我們可以查看,但實(shí)則并不存在該對(duì)象上。

          __proto__屬性既不能被 for in 遍歷出來(lái),也不能被 Object.keys(obj) 查找出來(lái)。

          訪(fǎng)問(wèn)對(duì)象的 obj.__proto__ 屬性,默認(rèn)走的是 Object.prototype 對(duì)象上 __proto__ 屬性的 get/set 方法。

          Object.defineProperty(Object.prototype,'__proto__',{
          get(){
          console.log('get')
          }
          });

          ({}).__proto__;
          console.log((new Object()).__proto__);

          關(guān)于更多__proto__更深入的介紹,可以參看工業(yè)聚大佬的《深入理解 JavaScript 原型》一文。

          這里我們需要知道的是,__proto__是對(duì)象所獨(dú)有的,并且__proto__一個(gè)對(duì)象指向另一個(gè)對(duì)象,也就是他的原型對(duì)象。我們也可以理解為父類(lèi)對(duì)象。它的作用就是當(dāng)你在訪(fǎng)問(wèn)一個(gè)對(duì)象屬性的時(shí)候,如果該對(duì)象內(nèi)部不存在這個(gè)屬性,那么就回去它的__proto__屬性所指向的對(duì)象(父類(lèi)對(duì)象)上查找,如果父類(lèi)對(duì)象依舊不存在這個(gè)屬性,那么就回去其父類(lèi)的__proto__屬性所指向的父類(lèi)的父類(lèi)上去查找。以此類(lèi)推,知道找到 null。而這個(gè)查找的過(guò)程,也就構(gòu)成了我們常說(shuō)的原型鏈。

          prototype

          object that provides shared properties for other objects

          在規(guī)范里,prototype 被定義為:給其它對(duì)象提供共享屬性的對(duì)象。prototype 自己也是對(duì)象,只是被用以承擔(dān)某個(gè)職能罷了.

          所有對(duì)象,都可以作為另一個(gè)對(duì)象的 prototype 來(lái)用。

          修改__proto__的關(guān)系圖,我們添加了 prototype,prototype是函數(shù)所獨(dú)有的。**它的作用就是包含可以給特定類(lèi)型的所有實(shí)例提供共享的屬性和方法。它的含義就是函數(shù)的遠(yuǎn)行對(duì)象,**也就是這個(gè)函數(shù)所創(chuàng)建的實(shí)例的遠(yuǎn)行對(duì)象,正如上圖:nealyang.__proto__ === Person.prototype。任何函數(shù)在創(chuàng)建的時(shí)候,都會(huì)默認(rèn)給該函數(shù)添加 prototype 屬性.

          constructor

          constructor屬性也是對(duì)象所獨(dú)有的,它是一個(gè)對(duì)象指向一個(gè)函數(shù),這個(gè)函數(shù)就是該對(duì)象的構(gòu)造函數(shù)。

          注意,每一個(gè)對(duì)象都有其對(duì)應(yīng)的構(gòu)造函數(shù),本身或者繼承而來(lái)。單從constructor這個(gè)屬性來(lái)講,只有prototype對(duì)象才有。每個(gè)函數(shù)在創(chuàng)建的時(shí)候,JavaScript 會(huì)同時(shí)創(chuàng)建一個(gè)該函數(shù)對(duì)應(yīng)的prototype對(duì)象,而函數(shù)創(chuàng)建的對(duì)象.__proto__ === 該函數(shù).prototype,該函數(shù).prototype.constructor===該函數(shù)本身,故通過(guò)函數(shù)創(chuàng)建的對(duì)象即使自己沒(méi)有constructor屬性,它也能通過(guò)__proto__找到對(duì)應(yīng)的constructor,所以任何對(duì)象最終都可以找到其對(duì)應(yīng)的構(gòu)造函數(shù)。

          唯一特殊的可能就是我開(kāi)篇拋出來(lái)的一個(gè)問(wèn)題。JavaScript 原型的老祖宗:Function。它是它自己的構(gòu)造函數(shù)。所以Function.prototype === Function.__proto。

          為了直觀(guān)了解,我們?cè)谏厦娴膱D中,繼續(xù)添加上constructor

          其中 constructor 屬性,虛線(xiàn)表示繼承而來(lái)的 constructor 屬性。

          __proto__介紹的原型鏈,我們?cè)趫D中直觀(guān)的標(biāo)出來(lái)的話(huà)就是如下這個(gè)樣子

          typeof && instanceof 原理

          問(wèn)什么好端端的說(shuō)原型、說(shuō)繼承會(huì)扯到類(lèi)型判斷的原理上來(lái)呢。畢竟原理上有一絲的聯(lián)系,往往面試也是由淺入深、順藤摸瓜的擰出整個(gè)知識(shí)面。所以這里我們也簡(jiǎn)單說(shuō)一下吧。

          typeof

          MDN 文檔點(diǎn)擊這里:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/typeof

          基本用法

          typeof 的用法相比大家都比較熟悉,一般被用于來(lái)判斷一個(gè)變量的類(lèi)型。我們可以使用 typeof 來(lái)判斷number、undefinedsymbol、stringfunction、booleanobject 這七種數(shù)據(jù)類(lèi)型。但是遺憾的是,typeof 在判斷 object 類(lèi)型時(shí)候,有些許的尷尬。它并不能明確的告訴你,該 object 屬于哪一種 object。

          let s = new String('abc');
          typeof s === 'object'// true
          typeof null;//"object"

          原理淺析

          要想弄明白為什么 typeof 判斷 nullobject,其實(shí)需要從js 底層如何存儲(chǔ)變量類(lèi)型來(lái)說(shuō)起。雖然說(shuō),這是 JavaScript 設(shè)計(jì)的一個(gè) bug。

          在 JavaScript 最初的實(shí)現(xiàn)中,JavaScript 中的值是由一個(gè)表示類(lèi)型的標(biāo)簽和實(shí)際數(shù)據(jù)值表示的。對(duì)象的類(lèi)型標(biāo)簽是 0。由于 null 代表的是空指針(大多數(shù)平臺(tái)下值為 0x00),因此,null 的類(lèi)型標(biāo)簽是 0,typeof null 也因此返回 "object"。曾有一個(gè) ECMAScript 的修復(fù)提案(通過(guò)選擇性加入的方式),但被拒絕了。該提案會(huì)導(dǎo)致 typeof null === 'null'。

          js 在底層存儲(chǔ)變量的時(shí)候,會(huì)在變量的機(jī)器碼的低位1-3位存儲(chǔ)其類(lèi)型信息:

          • 1:整數(shù)
          • 110:布爾
          • 100:字符串
          • 010:浮點(diǎn)數(shù)
          • 000:對(duì)象

          但是,對(duì)于 undefinednull 來(lái)說(shuō),這兩個(gè)值的信息存儲(chǔ)是有點(diǎn)特殊的:

          • null:所有機(jī)器碼均為0
          • undefined:用 ?2^30 整數(shù)來(lái)表示

          所以在用 typeof 來(lái)判斷變量類(lèi)型的時(shí)候,我們需要注意,最好是用 typeof 來(lái)判斷基本數(shù)據(jù)類(lèi)型(包括symbol),避免對(duì) null 的判斷。

          typeof 只是咱在討論原型帶出的 instanceof 的附加討論區(qū)

          instanceof

          object instanceof constructor

          instanceoftypeof 非常的類(lèi)似。instanceof 運(yùn)算符用來(lái)檢測(cè) constructor.prototype 是否存在于參數(shù) object 的原型鏈上。與 typeof 方法不同的是,instanceof 方法要求開(kāi)發(fā)者明確地確認(rèn)對(duì)象為某特定類(lèi)型。

          基本用法

          // 定義構(gòu)造函數(shù)
          function C(){}
          function D(){}

          var o = new C();


          o instanceof C; // true,因?yàn)?Object.getPrototypeOf(o) === C.prototype


          o instanceof D; // false,因?yàn)?D.prototype 不在 o 的原型鏈上

          o instanceof Object; // true,因?yàn)?Object.prototype.isPrototypeOf(o) 返回 true
          C.prototype instanceof Object // true,同上

          C.prototype = {};
          var o2 = new C();

          o2 instanceof C; // true

          o instanceof C; // false,C.prototype 指向了一個(gè)空對(duì)象,這個(gè)空對(duì)象不在 o 的原型鏈上.

          D.prototype = new C(); // 繼承
          var o3 = new D();
          o3 instanceof D; // true
          o3 instanceof C; // true 因?yàn)?C.prototype 現(xiàn)在在 o3 的原型鏈上

          如上,是 instanceof 的基本用法,它可以判斷一個(gè)實(shí)例是否是其父類(lèi)型或者祖先類(lèi)型的實(shí)例。

          console.log(Object instanceof Object);//true
          console.log(Function instanceof Function);//true
          console.log(Number instanceof Number);//false
          console.log(String instanceof String);//false

          console.log(Function instanceof Object);//true

          console.log(Foo instanceof Function);//true
          console.log(Foo instanceof Foo);//false

          為什么 ObjectFunction instanceof 自己等于 true,而其他類(lèi) instanceof 自己卻又不等于 true 呢?如何解釋?zhuān)?/p>

          要想從根本上了解 instanceof 的奧秘,需要從兩個(gè)方面著手:1,語(yǔ)言規(guī)范中是如何定義這個(gè)運(yùn)算符的。2,JavaScript 原型繼承機(jī)制。

          原理淺析

          經(jīng)過(guò)上述的分析,想必大家對(duì)這種經(jīng)典神圖已經(jīng)不那么陌生了吧,那咱就對(duì)著這張圖來(lái)聊聊 instanceof

          這里,我直接將規(guī)范定義翻譯為 JavaScript 代碼如下:

          function instance_of(L, R) {//L 表示左表達(dá)式,R 表示右表達(dá)式
          var O = R.prototype;// 取 R 的顯示原型
          L = L.__proto__;// 取 L 的隱式原型
          while (true) {
          if (L === null)
          return false;
          if (O === L)// 這里重點(diǎn):當(dāng) O 嚴(yán)格等于 L 時(shí),返回 true
          return true;
          L = L.__proto__;
          }
          }

          所以如上原理,加上上文解釋的原型相關(guān)知識(shí),我們?cè)賮?lái)解析下為什么ObjectFunction instanceof 自己等于 true

          • Object instanceof Object
          // 為了方便表述,首先區(qū)分左側(cè)表達(dá)式和右側(cè)表達(dá)式
          ObjectL = Object, ObjectR = Object;
          // 下面根據(jù)規(guī)范逐步推演
          O = ObjectR.prototype = Object.prototype
          L = ObjectL.__proto__ = Function.prototype
          // 第一次判斷
          O != L
          // 循環(huán)查找 L 是否還有 __proto__
          L = Function.prototype.__proto__ = Object.prototype
          // 第二次判斷
          O == L
          // 返回 true
          • Function instanceof Function
          // 為了方便表述,首先區(qū)分左側(cè)表達(dá)式和右側(cè)表達(dá)式
          FunctionL = Function, FunctionR = Function;
          // 下面根據(jù)規(guī)范逐步推演
          O = FunctionR.prototype = Function.prototype
          L = FunctionL.__proto__ = Function.prototype
          // 第一次判斷
          O == L
          // 返回 true
          • Foo instanceof Foo
          // 為了方便表述,首先區(qū)分左側(cè)表達(dá)式和右側(cè)表達(dá)式
          FooL = Foo, FooR = Foo;
          // 下面根據(jù)規(guī)范逐步推演
          O = FooR.prototype = Foo.prototype
          L = FooL.__proto__ = Function.prototype
          // 第一次判斷
          O != L
          // 循環(huán)再次查找 L 是否還有 __proto__
          L = Function.prototype.__proto__ = Object.prototype
          // 第二次判斷
          O != L
          // 再次循環(huán)查找 L 是否還有 __proto__
          L = Object.prototype.__proto__ = null
          // 第三次判斷
          L == null
          // 返回 false

          ES5 中的繼承實(shí)現(xiàn)方式

          在繼承實(shí)現(xiàn)上,工業(yè)聚大大在他的原型文章中,將原型繼承分為兩大類(lèi),顯式繼承和隱式繼承。感興趣的可以點(diǎn)擊文末參考鏈接查看。

          但是本文還是希望能夠基于“通俗”的方式來(lái)講解幾種常見(jiàn)的繼承方式和優(yōu)缺點(diǎn)。大家可多多對(duì)比查看,其實(shí)原理都是一樣,名詞也只是所謂的代稱(chēng)而已。

          關(guān)于繼承的文章,很多書(shū)本和博客中都有很詳細(xì)的講解。以下幾種繼承方式,均總結(jié)與《JavaScript 設(shè)計(jì)模式》一書(shū)。也是筆者三年前寫(xiě)的一篇文章了。

          new 關(guān)鍵字

          在講解繼承之前呢,我覺(jué)得 new 這個(gè)東西很有必要介紹下~

          一個(gè)例子看下new 關(guān)鍵字都干了啥

          function Person(name,age){
          this.name = name;
          this.age = age;

          this.sex = 'male';
          }

          Person.prototype.isHandsome = true;

          Person.prototype.sayName = function(){
          console.log(`Hello , my name is ${this.name}`);
          }

          let handsomeBoy = new Person('Nealyang',25);

          console.log(handsomeBoy.name) // Nealyang
          console.log(handsomeBoy.sex) // male
          console.log(handsomeBoy.isHandsome) // true

          handsomeBoy.sayName(); // Hello , my name is Nealyang

          從上面的例子我們可以看到:

          • 訪(fǎng)問(wèn)到 Person 構(gòu)造函數(shù)里的屬性
          • 訪(fǎng)問(wèn)到 Person.prototype 中的屬性

          new 手寫(xiě)版本一

          function objectFactory() {

          const obj = new Object(),//從Object.prototype上克隆一個(gè)對(duì)象

          Constructor = [].shift.call(arguments);//取得外部傳入的構(gòu)造器

          const F=function(){};
          F.prototype= Constructor.prototype;
          obj=new F();//指向正確的原型

          Constructor.apply(obj, arguments);//借用外部傳入的構(gòu)造器給obj設(shè)置屬性

          return obj;//返回 obj

          };
          • new Object() 的方式新建了一個(gè)對(duì)象 obj
          • 取出第一個(gè)參數(shù),就是我們要傳入的構(gòu)造函數(shù)。此外因?yàn)?shift 會(huì)修改原數(shù)組,所以 arguments 會(huì)被去除第一個(gè)參數(shù)
          • 將 obj 的原型指向構(gòu)造函數(shù),這樣 obj 就可以訪(fǎng)問(wèn)到構(gòu)造函數(shù)原型中的屬性
          • 使用 apply,改變構(gòu)造函數(shù) this 的指向到新建的對(duì)象,這樣 obj 就可以訪(fǎng)問(wèn)到構(gòu)造函數(shù)中的屬性
          • 返回 obj

          下面我們來(lái)測(cè)試一下:

          function Person(name,age){
          this.name = name;
          this.age = age;

          this.sex = 'male';
          }

          Person.prototype.isHandsome = true;

          Person.prototype.sayName = function(){
          console.log(`Hello , my name is ${this.name}`);
          }

          function objectFactory() {

          let obj = new Object(),//從Object.prototype上克隆一個(gè)對(duì)象

          Constructor = [].shift.call(arguments);//取得外部傳入的構(gòu)造器

          console.log({Constructor})

          const F=function(){};
          F.prototype= Constructor.prototype;
          obj=new F();//指向正確的原型

          Constructor.apply(obj, arguments);//借用外部傳入的構(gòu)造器給obj設(shè)置屬性

          return obj;//返回 obj

          };

          let handsomeBoy = objectFactory(Person,'Nealyang',25);

          console.log(handsomeBoy.name) // Nealyang
          console.log(handsomeBoy.sex) // male
          console.log(handsomeBoy.isHandsome) // true

          handsomeBoy.sayName(); // Hello , my name is Nealyang

          注意上面我們沒(méi)有直接修改 obj 的__proto__隱式掛載。

          new 手寫(xiě)版本二

          考慮構(gòu)造函數(shù)又返回值的情況:

          • 如果構(gòu)造函數(shù)返回一個(gè)對(duì)象,那么我們也返回這個(gè)對(duì)象
          • 如上否則,就返回默認(rèn)值
          function objectFactory() {

          var obj = new Object(),//從Object.prototype上克隆一個(gè)對(duì)象

          Constructor = [].shift.call(arguments);//取得外部傳入的構(gòu)造器

          var F=function(){};
          F.prototype= Constructor.prototype;
          obj=new F();//指向正確的原型

          var ret = Constructor.apply(obj, arguments);//借用外部傳入的構(gòu)造器給obj設(shè)置屬性

          return typeof ret === 'object' ? ret : obj;//確保構(gòu)造器總是返回一個(gè)對(duì)象

          };

          關(guān)于 call、apply、bind、this 等用法和原理講解:【THE LAST TIME】this:call、apply、bind

          類(lèi)式繼承

          function SuperClass() {
          this.superValue = true;
          }
          SuperClass.prototype.getSuperValue = function() {
          return this.superValue;
          }

          function SubClass() {
          this.subValue = false;
          }
          SubClass.prototype = new SuperClass();

          SubClass.prototype.getSubValue = function() {
          return this.subValue;
          }

          var instance = new SubClass();

          console.log(instance instanceof SuperClass)//true
          console.log(instance instanceof SubClass)//true
          console.log(SubClass instanceof SuperClass)//false

          從我們之前介紹的 instanceof 的原理我們知道,第三個(gè) console 如果這么寫(xiě)就返回 trueconsole.log(SubClass.prototype instanceof SuperClass)

          雖然實(shí)現(xiàn)起來(lái)清晰簡(jiǎn)潔,但是這種繼承方式有兩個(gè)缺點(diǎn):

          • 由于子類(lèi)通過(guò)其原型prototype對(duì)父類(lèi)實(shí)例化,繼承了父類(lèi),所以說(shuō)父類(lèi)中如果共有屬性是引用類(lèi)型,就會(huì)在子類(lèi)中被所有的實(shí)例所共享,因此一個(gè)子類(lèi)的實(shí)例更改子類(lèi)原型從父類(lèi)構(gòu)造函數(shù)中繼承的共有屬性就會(huì)直接影響到其他的子類(lèi)
          • 由于子類(lèi)實(shí)現(xiàn)的繼承是靠其原型prototype對(duì)父類(lèi)進(jìn)行實(shí)例化實(shí)現(xiàn)的,因此在創(chuàng)建父類(lèi)的時(shí)候,是無(wú)法向父類(lèi)傳遞參數(shù)的。因而在實(shí)例化父類(lèi)的時(shí)候也無(wú)法對(duì)父類(lèi)構(gòu)造函數(shù)內(nèi)的屬性進(jìn)行初始化

          構(gòu)造函數(shù)繼承

          function SuperClass(id) {
          this.books = ['js','css'];
          this.id = id;
          }
          SuperClass.prototype.showBooks = function() {
          console.log(this.books);
          }
          function SubClass(id) {
          //繼承父類(lèi)
          SuperClass.call(this,id);
          }
          //創(chuàng)建第一個(gè)子類(lèi)實(shí)例
          var instance1 = new SubClass(10);
          //創(chuàng)建第二個(gè)子類(lèi)實(shí)例
          var instance2 = new SubClass(11);

          instance1.books.push('html');
          console.log(instance1)
          console.log(instance2)
          instance1.showBooks();//TypeError

          SuperClass.call(this,id)當(dāng)然就是構(gòu)造函數(shù)繼承的核心語(yǔ)句了.由于父類(lèi)中給this綁定屬性,因此子類(lèi)自然也就繼承父類(lèi)的共有屬性。由于這種類(lèi)型的繼承沒(méi)有涉及到原型prototype,所以父類(lèi)的原型方法自然不會(huì)被子類(lèi)繼承,而如果想被子類(lèi)繼承,就必須放到構(gòu)造函數(shù)中,這樣創(chuàng)建出來(lái)的每一個(gè)實(shí)例都會(huì)單獨(dú)的擁有一份而不能共用,這樣就違背了代碼復(fù)用的原則,所以綜合上述兩種,我們提出了組合式繼承方法

          組合式繼承

          function SuperClass(name) {
          this.name = name;
          this.books = ['Js','CSS'];
          }
          SuperClass.prototype.getBooks = function() {
          console.log(this.books);
          }
          function SubClass(name,time) {
          SuperClass.call(this,name);
          this.time = time;
          }
          SubClass.prototype = new SuperClass();

          SubClass.prototype.getTime = function() {
          console.log(this.time);
          }

          如上,我們就解決了之前說(shuō)到的一些問(wèn)題,但是是不是從代碼看,還是有些不爽呢?至少這個(gè)SuperClass的構(gòu)造函數(shù)執(zhí)行了兩遍就感覺(jué)非常的不妥.

          原型式繼承

          function inheritObject(o) {
          //聲明一個(gè)過(guò)渡對(duì)象
          function F() { }
          //過(guò)渡對(duì)象的原型繼承父對(duì)象
          F.prototype = o;
          //返回過(guò)渡對(duì)象的實(shí)例,該對(duì)象的原型繼承了父對(duì)象
          return new F();
          }

          原型式繼承大致的實(shí)現(xiàn)方式如上,是不是想到了我們new關(guān)鍵字模擬的實(shí)現(xiàn)?

          其實(shí)這種方式和類(lèi)式繼承非常的相似,他只是對(duì)類(lèi)式繼承的一個(gè)封裝,其中的過(guò)渡對(duì)象就相當(dāng)于類(lèi)式繼承的子類(lèi),只不過(guò)在原型繼承中作為一個(gè)普通的過(guò)渡對(duì)象存在,目的是為了創(chuàng)建要返回的新的實(shí)例對(duì)象。

          var book = {
          name:'js book',
          likeBook:['css Book','html book']
          }
          var newBook = inheritObject(book);
          newBook.name = 'ajax book';
          newBook.likeBook.push('react book');
          var otherBook = inheritObject(book);
          otherBook.name = 'canvas book';
          otherBook.likeBook.push('node book');
          console.log(newBook,otherBook);

          如上代碼我們可以看出,原型式繼承和類(lèi)式繼承一個(gè)樣子,對(duì)于引用類(lèi)型的變量,還是存在子類(lèi)實(shí)例共享的情況。

          所以,我們還有下面的寄生式繼

          寄生式繼承

          var book = {
          name:'js book',
          likeBook:['html book','css book']
          }
          function createBook(obj) {
          //通過(guò)原型方式創(chuàng)建新的對(duì)象
          var o = new inheritObject(obj);
          // 拓展新對(duì)象
          o.getName = function(name) {
          console.log(name)
          }
          // 返回拓展后的新對(duì)象
          return o;
          }

          其實(shí)寄生式繼承就是對(duì)原型繼承的拓展,一個(gè)二次封裝的過(guò)程,這樣新創(chuàng)建的對(duì)象不僅僅有父類(lèi)的屬性和方法,還新增了別的屬性和方法。

          寄生組合式繼承

          回到之前的組合式繼承,那時(shí)候我們將類(lèi)式繼承和構(gòu)造函數(shù)繼承組合使用,但是存在的問(wèn)題就是子類(lèi)不是父類(lèi)的實(shí)例,而子類(lèi)的原型是父類(lèi)的實(shí)例,所以才有了寄生組合式繼承

          而寄生組合式繼承是寄生式繼承和構(gòu)造函數(shù)繼承的組合。但是這里寄生式繼承有些特殊,這里他處理不是對(duì)象,而是類(lèi)的原型。

          function inheritObject(o) {
          //聲明一個(gè)過(guò)渡對(duì)象
          function F() { }
          //過(guò)渡對(duì)象的原型繼承父對(duì)象
          F.prototype = o;
          //返回過(guò)渡對(duì)象的實(shí)例,該對(duì)象的原型繼承了父對(duì)象
          return new F();
          }

          function inheritPrototype(subClass,superClass) {
          // 復(fù)制一份父類(lèi)的原型副本到變量中
          var p = inheritObject(superClass.prototype);
          // 修正因?yàn)橹貙?xiě)子類(lèi)的原型導(dǎo)致子類(lèi)的constructor屬性被修改
          p.constructor = subClass;
          // 設(shè)置子類(lèi)原型
          subClass.prototype = p;
          }

          組合式繼承中,通過(guò)構(gòu)造函數(shù)繼承的屬性和方法都是沒(méi)有問(wèn)題的,所以這里我們主要探究通過(guò)寄生式繼承重新繼承父類(lèi)的原型。

          我們需要繼承的僅僅是父類(lèi)的原型,不用去調(diào)用父類(lèi)的構(gòu)造函數(shù)。換句話(huà)說(shuō),在構(gòu)造函數(shù)繼承中,我們已經(jīng)調(diào)用了父類(lèi)的構(gòu)造函數(shù)。因此我們需要的就是父類(lèi)的原型對(duì)象的一個(gè)副本,而這個(gè)副本我們可以通過(guò)原型繼承拿到,但是這么直接賦值給子類(lèi)會(huì)有問(wèn)題,因?yàn)閷?duì)父類(lèi)原型對(duì)象復(fù)制得到的復(fù)制對(duì)象p中的constructor屬性指向的不是subClass子類(lèi)對(duì)象,因此在寄生式繼承中要對(duì)復(fù)制對(duì)象p做一次增強(qiáng),修復(fù)起constructor屬性指向性不正確的問(wèn)題,最后將得到的復(fù)制對(duì)象p賦值給子類(lèi)原型,這樣子類(lèi)的原型就繼承了父類(lèi)的原型并且沒(méi)有執(zhí)行父類(lèi)的構(gòu)造函數(shù)。

          function SuperClass(name) {
          this.name = name;
          this.books=['js book','css book'];
          }
          SuperClass.prototype.getName = function() {
          console.log(this.name);
          }
          function SubClass(name,time) {
          SuperClass.call(this,name);
          this.time = time;
          }
          inheritPrototype(SubClass,SuperClass);
          SubClass.prototype.getTime = function() {
          console.log(this.time);
          }
          var instance1 = new SubClass('React','2017/11/11')
          var instance2 = new SubClass('Js','2018/22/33');

          instance1.books.push('test book');

          console.log(instance1.books,instance2.books);
          instance2.getName();
          instance2.getTime();

          這種方式繼承其實(shí)如上圖所示,其中最大的改變就是子類(lèi)原型中的處理,被賦予父類(lèi)原型中的一個(gè)引用,這是一個(gè)對(duì)象,因此有一點(diǎn)你需要注意,就是子類(lèi)在想添加原型方法必須通過(guò)prototype.來(lái)添加,否則直接賦予對(duì)象就會(huì)覆蓋從父類(lèi)原型繼承的對(duì)象了.

          ES6 類(lèi)的實(shí)現(xiàn)原理

          關(guān)于 ES6 中的 class 的一些基本用法和介紹,限于篇幅,本文就不做介紹了。該章節(jié),我們主要通過(guò) babel的 REPL來(lái)查看分析 es6 中各個(gè)語(yǔ)法糖包括繼承的一些實(shí)現(xiàn)方式。

          基礎(chǔ)類(lèi)

          我們就會(huì)按照這個(gè)類(lèi),來(lái)回摩擦。然后再來(lái)分析編譯后的代碼。

          "use strict";

          function _instanceof(left, right) {
          if (
          right != null &&
          typeof Symbol !== "undefined" &&
          right[Symbol.hasInstance]
          ) {
          return !!right[Symbol.hasInstance](left);
          } else {
          return left instanceof right;
          }
          }

          function _classCallCheck(instance, Constructor) {
          if (!_instanceof(instance, Constructor)) {
          throw new TypeError("Cannot call a class as a function");
          }
          }

          var Person = function Person(name) {
          _classCallCheck(this, Person);

          this.name = name;
          };

          _instanceof就是來(lái)判斷實(shí)例關(guān)系的的。上述代碼就比較簡(jiǎn)單了,_classCallCheck的作用就是檢查 Person 這個(gè)類(lèi),是否是通過(guò)new 關(guān)鍵字調(diào)用的。畢竟被編譯成 ES5 以后,function 可以直接調(diào)用,但是如果直接調(diào)用的話(huà),this 就指向 window 對(duì)象,就會(huì)Throw Error了.

          添加屬性

          "use strict";

          function _instanceof(left, right) {...}

          function _classCallCheck(instance, Constructor) {...}

          function _defineProperty(obj, key, value) {
          if (key in obj) {
          Object.defineProperty(obj, key, {
          value: value,
          enumerable: true,
          configurable: true,
          writable: true
          });
          } else {
          obj[key] = value;
          }
          return obj;
          }

          var Person = function Person(name) {
          _classCallCheck(this, Person);

          _defineProperty(this, "shili", '實(shí)例屬性');

          this.name = name;
          };

          _defineProperty(Person, "jingtai", ' 靜態(tài)屬性');

          其實(shí)就是講屬性賦值給誰(shuí)的問(wèn)題。如果是實(shí)例屬性,直接賦值到 this 上,如果是靜態(tài)屬性,則賦值類(lèi)上。_defineProperty也就是來(lái)判斷下是否屬性名重復(fù)而已。

          添加方法

          "use strict";

          function _instanceof(left, right) {...}

          function _classCallCheck(instance, Constructor) {...}

          function _defineProperty(obj, key, value) {...}

          function _defineProperties(target, props) {
          for (var i = 0; i < props.length; i++) {
          var descriptor = props[i];
          descriptor.enumerable = descriptor.enumerable || false;
          descriptor.configurable = true;
          if ("value" in descriptor) descriptor.writable = true;
          Object.defineProperty(target, descriptor.key, descriptor);
          }
          }

          function _createClass(Constructor, protoProps, staticProps) {
          if (protoProps) _defineProperties(Constructor.prototype, protoProps);
          if (staticProps) _defineProperties(Constructor, staticProps);
          return Constructor;
          }

          var Person =
          /*#__PURE__*/
          function () {
          function Person(name) {
          _classCallCheck(this, Person);

          _defineProperty(this, "shili", '實(shí)例屬性');

          this.name = name;
          }

          _createClass(Person, [{
          key: "sayName",
          value: function sayName() {
          return this.name;
          }
          }, {
          key: "name",
          get: function get() {
          return 'Nealyang';
          },
          set: function set(newName) {
          console.log('new name is :' + newName);
          }
          }], [{
          key: "eat",
          value: function eat() {
          return 'eat food';
          }
          }]);

          return Person;
          }();

          _defineProperty(Person, "jingtai", ' 靜態(tài)屬性');

          看起來(lái)代碼量還不少,其實(shí)就是一個(gè)_createClass函數(shù)和_defineProperties函數(shù)而已。

          首先看_createClass這個(gè)函數(shù)的三個(gè)參數(shù),第一個(gè)是構(gòu)造函數(shù),第二個(gè)是需要添加到原型上的函數(shù)數(shù)組,第三個(gè)是添加到類(lèi)本身的函數(shù)數(shù)組。其實(shí)這個(gè)函數(shù)的作用非常的簡(jiǎn)單。就是加強(qiáng)一下構(gòu)造函數(shù),所謂的加強(qiáng)構(gòu)造函數(shù)就是給構(gòu)造函數(shù)或者其原型上添加一些函數(shù)。

          _defineProperties就是多個(gè)_defineProperty(感覺(jué)是廢話(huà),不過(guò)的確如此)。默認(rèn) enumerablefalse,configurabletrue。

          其實(shí)如上就是 es6 class 的實(shí)現(xiàn)原理。

          extend 關(guān)鍵字

          "use strict";

          function _instanceof(left, right) {...}

          function _classCallCheck(instance, Constructor) {...}

          var Parent = function Parent(name) {...};

          function _typeof(obj) {
          if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
          _typeof = function _typeof(obj) {
          return typeof obj;
          };
          } else {
          _typeof = function _typeof(obj) {
          return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
          };
          }
          return _typeof(obj);
          }

          function _possibleConstructorReturn(self, call) {
          if (call && (_typeof(call) === "object" || typeof call === "function")) {
          return call;
          }
          return _assertThisInitialized(self);
          }

          function _assertThisInitialized(self) {
          if (self === void 0) {
          throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
          }
          return self;
          }

          function _getPrototypeOf(o) {
          _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) {
          return o.__proto__ || Object.getPrototypeOf(o);
          };
          return _getPrototypeOf(o);
          }

          function _inherits(subClass, superClass) {
          if (typeof superClass !== "function" && superClass !== null) {
          throw new TypeError("Super expression must either be null or a function");
          }
          subClass.prototype = Object.create(superClass && superClass.prototype, {
          constructor: {
          value: subClass,
          writable: true,
          configurable: true
          }
          });
          if (superClass) _setPrototypeOf(subClass, superClass);
          }

          function _setPrototypeOf(o, p) {
          _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
          o.__proto__ = p;
          return o;
          };
          return _setPrototypeOf(o, p);
          }

          var Child =
          /*#__PURE__*/
          function (_Parent) {
          _inherits(Child, _Parent);

          function Child(name, age) {
          var _this;

          _classCallCheck(this, Child);

          _this = _possibleConstructorReturn(this, _getPrototypeOf(Child).call(this, name)); // 調(diào)用父類(lèi)的 constructor(name)

          _this.age = age;
          return _this;
          }

          return Child;
          }(Parent);

          var child1 = new Child('全棧前端精選', '0.3');
          console.log(child1);

          刪去類(lèi)相關(guān)的代碼生成,剩下的就是繼承的語(yǔ)法糖剖析了。其中super 關(guān)鍵字表示父類(lèi)的構(gòu)造函數(shù),相當(dāng)于 ES5 的 Parent.call(this),然后再根據(jù)我們上文說(shuō)到的繼承方式,有沒(méi)有感覺(jué)該集成的實(shí)現(xiàn)跟我們說(shuō)的寄生組合式繼承非常的相似呢?

          在 ES6 class 中,子類(lèi)必須在 constructor 方法中調(diào)用 super 方法,否則新建實(shí)例時(shí)會(huì)報(bào)錯(cuò)。這是因?yàn)?strong>子類(lèi)沒(méi)有自己的 this 對(duì)象,而是繼承父類(lèi)的 this 對(duì)象,然后對(duì)其進(jìn)行加工。如果不調(diào)用 super 方法,子類(lèi)就得不到 this 對(duì)象。

          也正是因?yàn)檫@個(gè)原因,在子類(lèi)的構(gòu)造函數(shù)中,只有調(diào)用 super 之后,才可以使用 this 關(guān)鍵字,否則會(huì)報(bào)錯(cuò)。

          關(guān)于 ES6 中原型鏈?zhǔn)疽鈭D可以參照如下示意圖:

          圖片來(lái)自?xún)暧鸬牟┛?/figcaption>

          關(guān)于ES6 中的 extend 關(guān)鍵字,上述代碼我們完全可以根據(jù)執(zhí)行來(lái)看。其實(shí)重點(diǎn)代碼無(wú)非就兩行:

          _inherits(Child, _Parent);
          _this = _possibleConstructorReturn(this, _getPrototypeOf(Child).call(this, name));

          我們分別來(lái)分析下具體的實(shí)現(xiàn):

          _inherits

          代碼比較簡(jiǎn)單,都是上文提到的內(nèi)容,就是建立 Child 和 Parent 的原型鏈關(guān)系。代碼解釋已備注在代碼內(nèi)

          function _inherits(subClass, superClass) {
          if (typeof superClass !== "function" && superClass !== null) {//subClass 類(lèi)型判斷
          throw new TypeError("Super expression must either be null or a function");
          }
          subClass.prototype = Object.create(superClass && superClass.prototype, {
          constructor: {//Object.create 第二個(gè)參數(shù)是給subClass.prototype添加了 constructor 屬性
          value: subClass,
          writable: true,
          configurable: true//注意這里enumerable沒(méi)有指名,默認(rèn)是 false,也就是說(shuō)constructor為不可枚舉的。
          }
          });
          if (superClass) _setPrototypeOf(subClass, superClass);
          }

          function _setPrototypeOf(o, p) {
          _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
          o.__proto__ = p;
          return o;
          };
          return _setPrototypeOf(o, p);
          }

          _possibleConstructorReturn

          _this = _possibleConstructorReturn(this, _getPrototypeOf(Child).call(this, name));

          根據(jù)上圖我們整理的 es6 原型圖可知:

          Child.prototype === Parent

          所以上面的代碼我們可以翻譯為:

          _this = _possibleConstructorReturn(this, Parent.call(this, name));

          然后我們?cè)僖粚右粚訐茉创a的實(shí)現(xiàn)

          function _possibleConstructorReturn(self, call) {
          if (call && (_typeof(call) === "object" || typeof call === "function")) {
          return call;
          }
          return _assertThisInitialized(self);
          }

          function _assertThisInitialized(self) {
          if (self === void 0) {
          throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
          }
          return self;
          }

          上述代碼,self其實(shí)就是 Child 的 IIFE返回的 function new 調(diào)用的 this,打印出來(lái)結(jié)果如下:

          這里可能對(duì)Parent.call(this,name)有些疑惑,沒(méi)關(guān)系,我們可以在 Chrome 下調(diào)試下。

          可以看到,當(dāng)我們 Parent 的構(gòu)造函數(shù)這么寫(xiě)

          class Parent {
          constructor(name) {
          this.name = name;
          }
          }

          那么最終,傳遞給_possibleConstructorReturn函數(shù)的第二參數(shù) call就是一個(gè) undefined。所以在_possibleConstructorReturn函數(shù)里面會(huì)對(duì) call進(jìn)行判斷,返回正確的 this 指向:Child。

          所以整體代碼的目的就是根據(jù) Parent 構(gòu)造函數(shù)的返回值類(lèi)型確定子類(lèi)構(gòu)造函數(shù) this 的初始值 _this

          最后

          【THE LAST TIME】系列關(guān)于 JavaScript 基礎(chǔ)的文章目前更新三篇,我們最后再來(lái)一道經(jīng)典的面試題吧!

          function Foo() {
          getName = function() {
          alert(1);
          };
          return this;
          }
          Foo.getName = function() {
          alert(2);
          };
          Foo.prototype.getName = function() {
          alert(3);
          };
          var getName = function() {
          alert(4);
          };
          function getName() {
          alert(5);
          }

          //請(qǐng)寫(xiě)出以下輸出結(jié)果:
          Foo.getName();
          getName();
          Foo().getName();
          getName();
          new Foo.getName();
          new Foo().getName();
          new new Foo().getName();

          老鐵,評(píng)論區(qū)留下你的思考吧~

          參考文獻(xiàn)

          • 深入理解 JavaScript 原型
          • 幫你徹底搞懂JS中的prototype、__proto__與constructor
          • JavaScript instanceof 運(yùn)算符深入剖析
          • JavaScript深入之創(chuàng)建對(duì)象的多種方式以及優(yōu)缺點(diǎn)
          • ES6 系列之 Babel 是如何編譯 Class 的(上)
          • ES6—類(lèi)的實(shí)現(xiàn)原理
          • es6類(lèi)和繼承的實(shí)現(xiàn)原理
          • JavaScript深入之new的模擬實(shí)現(xiàn)


          ?? 看完三件事

          如果你覺(jué)得這篇內(nèi)容對(duì)你挺有啟發(fā),我想邀請(qǐng)你幫我三個(gè)小忙:

          • 點(diǎn)個(gè)【在看】,或者分享轉(zhuǎn)發(fā),讓更多的人也能看到這篇內(nèi)容

          • 關(guān)注公眾號(hào)【全棧前端精選】,不定期分享原創(chuàng)&精品技術(shù)文章。

          • 公眾號(hào)內(nèi)回復(fù):【 1 】。加入全棧前端精選公眾號(hào)交流群。


          歡迎評(píng)論區(qū)留下你的精彩評(píng)論~

          覺(jué)得文章不錯(cuò)可以分享到朋友圈讓更多的小伙伴看到哦~

          客官!在看一下唄
          瀏覽 57
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  啊啊啊啊啊www. | 国产精品乱码一区二区免费视频 | 色九九| 久久新视频 | 精品人妻无码系列 |