如何繼承 Date 對象?由一道題徹底弄懂 JS 繼承

2021-12-09 SegmentFault思否
前言

故事是從一次實際需求中開始的。。。

某天,某人向我尋求了一次幫助,要協助寫一個日期工具類,要求:

形象點描述,就是要求可以這樣:

// 假設最終的類是 MyDate,有一個getTest拓展方法

let date = new MyDate();

// 調用Date的方法,輸出GMT絕對毫秒數

console.log(date.getTime());

// 調用拓展的方法,隨便輸出什麼,譬如helloworld!

console.log(date.getTest());

於是,隨手用JS中經典的組合寄生法寫了一個繼承,然後,剛準備完美收工,一運行,卻出現了以下的情景:

但是的心情是這樣的: 😳囧

以前也沒有遇到過類似的問題,然後自己嘗試著用其它方法,多次嘗試,均無果(不算暴力混合法的情況),其實回過頭來看,是因為思路新奇,憑空想不到,並不是原理上有多難。。。

於是,藉助強大的搜素引擎,搜集資料,最後,再自己總結了一番,才有了本文。

正文開始前,各位看官可以先暫停往下讀,嘗試下,在不藉助任何網絡資料的情況下,是否能實現上面的需求?(就以 10分鐘為限吧)

分析問題的關鍵

藉助stackoverflow上的回答。

經典的繼承法有何問題

先看看本文最開始時提到的經典繼承法實現,如下:

/**

* 經典的js組合寄生繼承

*/

function MyDate() {

   Date.apply(this, arguments);

   this.abc = 1;

}

function inherits(subClass, superClass) {

   function Inner() {}

   Inner.prototype = superClass.prototype;

   subClass.prototype = new Inner();

   subClass.prototype.constructor = subClass;

}

inherits(MyDate, Date);

MyDate.prototype.getTest = function() {

   return this.getTime();

};

let date = new MyDate();

console.log(date.getTest());

就是這段代碼⬆,這也是JavaScript高程(紅寶書)中推薦的一種,一直用,從未失手,結果現在馬失前蹄。。。

我們再回顧下它的報錯:

再列印它的原型看看:

怎麼看都沒問題,因為按照原型鏈回溯規則, Date的所有原型方法都可以通過 MyDate對象的原型鏈往上回溯到。再仔細看看,發現它的關鍵並不是找不到方法,而是 thisisnotaDateobject.

嗯哼,也就是說,關鍵是:由於調用的對象不是Date的實例,所以不允許調用,就算是自己通過原型繼承的也不行。

為什麼無法被繼承?

首先,看看 MDN上的解釋,上面有提到,JavaScript的日期對象只能通過 JavaScriptDate作為構造函數來實例化。

然後再看看stackoverflow上的回答:

有提到, v8引擎底層代碼中有限制,如果調用對象的 [[Class]]不是 Date,則拋出錯誤。

總的來說,結合這兩點,可以得出一個結論:要調用Date上方法的實例對象必須通過Date構造出來,否則不允許調用Date的方法。

該如何實現繼承?

雖然原因找到了,但是問題仍然要解決啊,真的就沒辦法了麼?當然不是,事實上還是有不少實現的方法的。

暴力混合法

首先,說說說下暴力的混合法,它是下面這樣子的:

說到底就是:內部生成一個 Date對象,然後此類暴露的方法中,把原有 Date中所有的方法都代理一遍,而且嚴格來說,這根本算不上繼承(都沒有原型鏈回溯)。

ES5黑魔法

然後,再看看ES5中如何實現?

// 需要考慮polyfill情況

Object.setPrototypeOf = Object.setPrototypeOf ||

function(obj, proto) {

   obj.__proto__ = proto;

   return obj;

};

/**

* 用了點技巧的繼承,實際上返回的是Date對象

*/

function MyDate() {

   // bind屬於Function.prototype,接收的參數是:object, param1, params2...

   var dateInst = new(Function.prototype.bind.apply(Date, [Date].concat(Array.prototype.slice.call(arguments))))();

   // 更改原型指向,否則無法調用MyDate原型上的方法

   // ES6方案中,這裡就是[[prototype]]這個隱式原型對象,在沒有標準以前就是__proto__

   Object.setPrototypeOf(dateInst, MyDate.prototype);

   dateInst.abc = 1;

   return dateInst;

}

// 原型重新指回Date,否則根本無法算是繼承

Object.setPrototypeOf(MyDate.prototype, Date.prototype);

MyDate.prototype.getTest = function getTest() {

   return this.getTime();

};

let date = new MyDate();

// 正常輸出,譬如1515638988725

console.log(date.getTest());

一眼看上去不知所措?沒關係,先看下圖來理解:(原型鏈關係一目了然)

可以看到,用的是非常巧妙的一種做法:

正常繼承的情況如下:

這種做法的繼承的情況如下:

可以看出,關鍵點在於:

所以最終的實例對象仍然能進行正常的原型鏈回溯,回溯到原本Date的所有原型方法。

這樣通過一個巧妙的欺騙技巧,就實現了完美的Date繼承。不過補充一點, MDN上有提到儘量不要修改對象的 [[Prototype]],因為這樣可能會幹涉到瀏覽器本身的優化。如果你關心性能,你就不應該在一個對象中修改它的 [[Prototype]]

ES6大法

當然,除了上述的ES5實現,ES6中也可以直接繼承(自帶支持繼承 Date),而且更為簡單:

class MyDate extends Date {

   constructor() {

       super();

       this.abc = 1;

   }

   getTest() {

       return this.getTime();

   }

}

let date = new MyDate();

// 正常輸出,譬如1515638988725

console.log(date.getTest());

對比下ES5中的實現,這個真的是簡單的不行,直接使用ES6的Class語法就行了。而且,也可以正常輸出。

注意:這裡的正常輸出環境是直接用ES6運行,不經過babel打包,打包後實質上是轉化成ES5的,所以效果完全不一樣。

ES6寫法,然後Babel打包

雖然說上述ES6大法是可以直接繼承Date的,但是,考慮到實質上大部分的生產環境是: ES6+Babel

直接這樣用ES6 + Babel是會出問題的。

不信的話,可以自行嘗試下,Babel打包成ES5後代碼大致是這樣的:

然後當信心滿滿的開始用時,會發現:

對,又出現了這個問題,也許這時候是這樣的⊙?⊙

因為轉譯後的ES5源碼中,仍然是通過 MyDate來構造,而 MyDate的構造中又無法修改屬於 Date內部的 [[Class]]之類的私有標誌,因此構造出的對象仍然不允許調用 Date方法(調用時,被引擎底層代碼識別為 [[Class]]標誌不符合,不允許調用,拋出錯誤)。

由此可見,ES6繼承的內部實現和Babel打包編譯出來的實現是有區別的。(雖說Babel的polyfill一般會按照定義的規範去實現的,但也不要過度迷信)。

幾種繼承的細微區別

雖然上述提到的三種方法都可以達到繼承 Date的目的-混合法嚴格說不能算繼承,只不過是另類實現。

於是,將所有能列印的主要信息都列印出來,分析幾種繼承的區別,大致場景是這樣的:

可以參考:( 請進入調試模式)https://dailc.github.io/fe-interview/demo/extends_date.html

從上往下, 1,2,3,4四種繼承實現分別是:(排出了混合法)

~~~~以下是MyDate們的prototype~~~~~~~~~

Date {constructor: ƒ, getTest: ƒ}

Date {constructor: ƒ, getTest: ƒ}

Date {getTest: ƒ, constructor: ƒ}

Date {constructor: ƒ, getTest: ƒ}

~~~~以下是new出的對象~~~~~~~~~

Sat Jan 13 2018 21:58:55 GMT+0800 (CST)

MyDate2 {abc: 1}

Sat Jan 13 2018 21:58:55 GMT+0800 (CST)

MyDate {abc: 1}

~~~~以下是new出的對象的Object.prototype.toString.call~~~~~~~~~

[object Date]

[object Object]

[object Date]

[object Object]

~~~~以下是MyDate們的__proto__~~~~~~~~~

ƒ Date() { [native code] }

ƒ () { [native code] }

ƒ () { [native code] }

ƒ Date() { [native code] }

~~~~以下是new出的對象的__proto__~~~~~~~~~

Date {constructor: ƒ, getTest: ƒ}

Date {constructor: ƒ, getTest: ƒ}

Date {getTest: ƒ, constructor: ƒ}

Date {constructor: ƒ, getTest: ƒ}

~~~~以下是對象的__proto__與MyDate們的prototype比較~~~~~~~~~

true

true

true

true

看出,主要差別有幾點:

MyDate們的proto指向不一樣

Object.prototype.toString.call的輸出不一樣

對象本質不一樣,可以正常調用的 1,3都是 Date構造出的,而其它的則是 MyDate構造出的

我們上文中得出的一個結論是:由於調用的對象不是由Date構造出的實例,所以不允許調用,就算是自己的原型鏈上有Date.prototype也不行

但是這裡有兩個變量:分別是底層構造實例的方法不一樣,以及對象的 Object.prototype.toString.call的輸出不一樣(另一個 MyDate.__proto__可以排除,因為原型鏈回溯肯定與它無關)。

萬一它的判斷是根據 Object.prototype.toString.call來的呢?那這樣結論不就有誤差了?

於是,根據ES6中的, Symbol.toStringTag,使用黑魔法,動態的修改下它,排除下幹擾:

// 分別可以給date2,date3設置

Object.defineProperty(date2, Symbol.toStringTag, {

   get: function() {

       return "Date";

   }

});

然後在列印下看看,變成這樣了:

[object Date]

[object Date]

[object Date]

[object Object]

可以看到,第二個的 MyDate2構造出的實例,雖然列印出來是 [objectDate],但是調用Date方法仍然是有錯誤。

此時我們可以更加準確一點的確認:由於調用的對象不是由Date構造出的實例,所以不允許調用

而且我們可以看到,就算通過黑魔法修改 Object.prototype.toString.call,內部的 [[Class]]標識位也是無法修改的。(這塊知識點大概是Object.prototype.toString.call可以輸出內部的[[Class]],但無法改變它,由於不是重點,這裡不贅述)。

ES6繼承與ES5繼承的區別

從上午中的分析可以看到一點:ES6的Class寫法繼承是沒問題的。但是換成ES5寫法就不行了。

所以ES6的繼承大法和ES5肯定是有區別的,那麼究竟是哪裡不同呢?(主要是結合的本文繼承Date來說)

區別:(以 SubClass, SuperClass, instance為例)

ES5中繼承的實質是:(那種經典組合寄生繼承法)

先由子類( SubClass)構造出實例對象this

然後在子類的構造函數中,將父類( SuperClass)的屬性添加到 this上, SuperClass.apply(this,arguments)

子類原型( SubClass.prototype)指向父類原型( SuperClass.prototype)

所以 instance是子類( SubClass)構造出的(所以沒有父類的 [[Class]]關鍵標誌)

所以, instance有 SubClass和 SuperClass的所有實例屬性,以及可以通過原型鏈回溯,獲取 SubClass和 SuperClass原型上的方法

ES6中繼承的實質是:

先由父類( SuperClass)構造出實例對象this,這也是為什麼必須先調用父類的 super()方法(子類沒有自己的this對象,需先由父類構造)

然後在子類的構造函數中,修改this(進行加工),譬如讓它指向子類原型( SubClass.prototype),這一步很關鍵,否則無法找到子類原型(注,子類構造中加工這一步的實際做法是推測出的,從最終效果來推測)

然後同樣,子類原型( SubClass.prototype)指向父類原型( SuperClass.prototype)

所以 instance是父類( SuperClass)構造出的(所以有著父類的 [[Class]]關鍵標誌)

所以, instance有 SubClass和 SuperClass的所有實例屬性,以及可以通過原型鏈回溯,獲取 SubClass和 SuperClass原型上的方法

以上⬆就列舉了些重要信息,其它的如靜態方法的繼承沒有贅述。(靜態方法繼承實質上只需要更改下 SubClass.__proto__到 SuperClass即可)

可以看著這張圖快速理解:

有沒有發現呢:ES6中的步驟和本文中取巧繼承Date的方法一模一樣,不同的是ES6是語言底層的做法,有它的底層優化之處,而本文中的直接修改_proto_容易影響性能

ES6中在super中構建this的好處?

因為ES6中允許我們繼承內置的類,如Date,Array,Error等。如果this先被創建出來,在傳給Array等系統內置類的構造函數,這些內置類的構造函數是不認這個this的。所以需要現在super中構建出來,這樣才能有著super中關鍵的 [[Class]]標誌,才能被允許調用。(否則就算繼承了,也無法調用這些內置類的方法)

構造函數與實例對象

看到這裡,不知道是否對上午中頻繁提到的構造函數實例對象有所混淆與困惑呢?這裡稍微描述下。

要弄懂這一點,需要先知道 new一個對象到底發生了什麼?先形象點說:

new MyClass()中,都做了些什麼工作

function MyClass() {

   this.abc = 1;

}

MyClass.prototype.print = function() {

   console.log('this.abc:' + this.abc);

};

let instance = new MyClass();

譬如,上述就是一個標準的實例對象生成,都發生了什麼呢?

步驟簡述如下:(參考MDN,還有部分關於底層的描述略去-如[[Class]]標識位等)

構造函數內部,創建一個新的對象,它繼承自 MyClass.prototype, letinstance=Object.create(MyClass.prototype);

使用指定的參數調用構造函數 MyClass,並將 this綁定到新創建的對象, MyClass.call(instance);,執行後擁有所有實例屬性

如果構造函數返回了一個「對象」,那麼這個對象會取代整個 new出來的結果。如果構造函數沒有返回對象,那麼new出來的結果為步驟1創建的對象。 (一般情況下構造函數不返回任何值,不過用戶如果想覆蓋這個返回值,可以自己選擇返回一個普通對象來覆蓋。當然,返回數組也會覆蓋,因為數組也是對象。)

結合上述的描述,大概可以還原成以下代碼(簡單還原,不考慮各種其它邏輯):

let instance = Object.create(MyClass.prototype);

let innerConstructReturn = MyClass.call(instance);

let innerConstructReturnIsObj = typeof innerConstructReturn === 'object' || typeof innerConstructReturn === 'function';

return innerConstructReturnIsObj ? innerConstructReturn : instance;

注意⚠️:普通的函數構建,可以簡單的認為就是上述步驟。實際上對於一些內置類(如Date等),並沒有這麼簡單,還有一些自己的隱藏邏輯,譬如 [[Class]]標識位等一些重要私有屬性。譬如可以在MDN上看到,以常規函數調用Date(即不加 new 操作符)將會返回一個字符串,而不是一個日期對象,如果這樣模擬的話會無效。

覺得看起來比較繁瑣?可以看下圖梳理:

那現在再回頭看看。

什麼是構造函數?

如上述中的 MyClass就是一個構造函數,在內部它構造出了 instance對象。

什麼是實例對象?

instance就是一個實例對象,它是通過 new出來的?

實例與構造的關係

有時候淺顯點,可以認為構造函數是xxx就是xxx的實例。即:

let instance = new MyClass();

此時我們就可以認為 instance是 MyClass的實例,因為它的構造函數就是它。

實例就一定是由對應的構造函數構造出的麼?

不一定,我們那ES5黑魔法來做示例。

function MyDate() {

   // bind屬於Function.prototype,接收的參數是:object, param1, params2...

   var dateInst = new(Function.prototype.bind.apply(Date, [Date].concat(Array.prototype.slice.call(arguments))))();

   // 更改原型指向,否則無法調用MyDate原型上的方法

   // ES6方案中,這裡就是[[prototype]]這個隱式原型對象,在沒有標準以前就是__proto__

   Object.setPrototypeOf(dateInst, MyDate.prototype);

   dateInst.abc = 1;

   return dateInst;

}

我們可以看到 instance的最終指向的原型是 MyDate.prototype,而 MyDate.prototype的構造函數是 MyDate,因此可以認為 instance是 MyDate的實例。

但是,實際上, instance卻是由 Date構造的,我們可以繼續用 ES6中的 new.target來驗證。

注意⚠️:關於 new.target, MDN中的定義是:new.target返回一個指向構造方法或函數的引用

嗯哼,也就是說,返回的是構造函數。

我們可以在相應的構造中測試列印:

class MyDate extends Date {

   constructor() {

       super();

       this.abc = 1;

       console.log('~~~new.target.name:MyDate~~~~');

       console.log(new.target.name);

   }

}

// new操作時的列印結果是:

// ~~~new.target.name:MyDate~~~~

// MyDate

然後,可以在上面的示例中看到,就算是ES6的Class繼承, MyDate構造中列印 new.target也顯示 MyDate,但實際上它是由 Date來構造(有著 Date關鍵的 [[Class]]標誌,因為如果不是Date構造(如沒有標誌)是無法調用Date的方法的)。

這也算是一次小小的勘誤吧。

所以,實際上用 new.target是無法判斷實例對象到底是由哪一個構造構造的(這裡指的是判斷底層真正的 [[Class]]標誌來源的構造)

再回到結論:實例對象不一定就是由它的原型上的構造函數構造的,有可能構造函數內部有著寄生等邏輯,偷偷的用另一個函數來構造了下,當然,簡單情況下,我們直接說實例對象由對應構造函數構造也沒錯(不過,在涉及到這種Date之類的分析時,我們還是得明白)。

[[Class]]與Internal slot

這一部分為補充內容。

前文中一直提到一個概念:Date內部的 [[Class]]標識

其實,嚴格來說,不能這樣泛而稱之(前文中只是用這個概念是為了降低複雜度,便於理解),它可以分為以下兩部分:

在ES5中,每種內置對象都定義了 [[Class]] 內部屬性的值,[[Class]] 內部屬性的值用於內部區分對象的種類

Object.prototype.toString訪問的就是這個[[Class]]

規範中除了通過 Object.prototype.toString,沒有提供任何手段使程序訪問此值。

而且Object.prototype.toString輸出無法被修改

而在ES5中,之前的 [[Class]] 不再使用,取而代之的是一系列的 internalslot

Internal slot 對應於與對象相關聯並由各種ECMAScript規範算法使用的內部狀態,它們沒有對象屬性,也不能被繼承

根據具體的 Internal slot 規範,這種狀態可以由任何ECMAScript語言類型或特定ECMAScript規範類型值的值組成

通過 Object.prototype.toString,仍然可以輸出Internal slot值

簡單點理解(簡化理解),Object.prototype.toString的流程是:如果是基本數據類型(除去Object以外的幾大類型),則返回原本的slot,如果是Object類型(包括內置對象以及自己寫的對象),則調用 Symbol.toStringTag。 Symbol.toStringTag方法的默認實現就是返回對象的Internal slot,這個方法可以被重寫

這兩點是有所差異的,需要區分(不過簡單點可以統一理解為內置對象內部都有一個特殊標識,用來區分對應類型-不符合類型就不給調用)。

JS內置對象是這些:

"Arguments", "Array", "Boolean", "Date", "Error", "Function", "JSON", "Math", "Number", "Object", "RegExp", "String"

ES6新增的一些,這裡未提到:(如Promise對象可以輸出 [objectPromise]),而前文中提到的:

Object.defineProperty(date, Symbol.toStringTag, {

   get: function() {

       return "Date";

   }

});

它的作用是重寫Symbol.toStringTag,截取date(雖然是內置對象,但是仍然屬於Object)的 Object.prototype.toString的輸出,讓這個對象輸出自己修改後的 [objectDate]。

但是,僅僅是做到輸出的時候變成了Date,實際上內部的 internalslot值並沒有被改變,因此仍然不被認為是Date。

如何快速判斷是否繼承?

其實,在判斷繼承時,沒有那麼多的技巧,就只有關鍵的一點: [[prototype]]( __ptoto__)的指向關係

譬如:

console.log(instance instanceof SubClass);

console.log(instance instanceof SuperClass);

實質上就是:

然後,對照本文中列舉的一些圖,一目了然就可以看清關係。有時候,完全沒有必要弄的太複雜。

寫在最後的話

由於繼承的介紹在網上已經多不勝數,因此本文沒有再重複描述,而是由一道Date繼承題引發,展開(關鍵就是原型鏈)。

不知道看到這裡,各位看官是否都已經弄懂了JS中的繼承呢?

另外,遇到問題時,多想一想,有時候你會發現,其實你知道的並不是那麼多,然後再想一想,又會發現其實並沒有這麼複雜。。。

初次發布 2018.01.15 於我個人博客上面:

http://www.dailichun.com/2018/01/15/howtoextenddate.html

參考資料

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes

https://stackoverflow.com/questions/6075231/how-to-extend-the-javascript-date-object/30882416

http://exploringjs.com/es6/chclasses.html#secessentials-classes

http://blog.csdn.net/github_36978270/article/details/71896444

http://blog.csdn.net/pcaxb/article/details/53784309

相關文章推薦

讓我印象深刻的 javascript 面試題

思路清奇:通過 JavaScript 獲取行動裝置的型號

詳解 js 閉包

歡迎關注 SegmentFault 微信公眾號 :)

相關焦點

  • 【每日一題】(41題)JS代碼到底是如何被壓縮的?
    那它是如何完成一些壓縮功能的,比如替換空白符,答案是 AST。UglifyJS 功能:•生成JS代碼的抽象語法樹(AST),通過parse-js.js完成.•遍歷AST語法樹,做各種操作,比如自動縮進、縮短變量名、刪除塊括號{}、去空格、常量表達式、連續變量聲明、語塊合併、去掉無法訪問的代碼等,通過process.js完成.
  • JavaScript繼承方式詳解
    js繼承的概念js裡常用的如下兩種繼承方式:
  • JS必知的6種繼承方式
    那麼如何在JS中實現繼承呢?Object.create()就是這個原理特點:類似於複製一個對象,用函數來包裝缺點:5、寄生式繼承function content(obj) {  function F() {}  F.prototype = obj; // 繼承了傳入的參數  return new F(); // 返回函數對象}
  • 重學 JS 系列:聊聊繼承
    其實原型的概念很簡單:•所有對象都有一個屬性 __proto__ 指向一個對象,也就是原型•每個對象的原型都可以通過 constructor 找到構造函數,構造函數也可以通過 prototype 找到原型•所有函數都可以通過 __proto__ 找到 Function 對象•所有對象都可以通過 __proto__ 找到 Object 對象•對象之間通過 __proto__
  • 由一道面試題引發的JS計時器花式玩法
    // 每日前端夜話 第400篇// 正文共:3100 字// 預計閱讀時間:10 分鐘先從一個面試題開始:❝JavaScript 面試題:setTimeout 和 setInterval 的原始碼是在哪裡實現的
  • C語言對象編程第二彈:繼承
    C語言對象編程的繼承與抽象。本次分享C語言對象編程第二彈:繼承。繼承簡單說來就是父親有的東西,孩子可以繼承過來。當創建一個類時,我們不需要重新編寫新的數據成員和成員函數,只需指定新建的類繼承了一個已有的類的成員即可。
  • C 語言如何實現繼承與容器
    如果文中有不對的地方,還請各位朋友能及時地給我指出來,我將不勝感激,謝謝~繼承的概念繼承是面向對象軟體技術當中的一個概念,與多態、封裝共為面向對象的三個基本特徵。繼承可以使得子類具有父類的屬性和方法或者重新定義,追加屬性和方法。面向對象中的重要概念就是類,在我們熟知的程式語言 C++ 、Python 中都存在類的概念,通過現有的類從而繼承得到新的類。
  • JAVA基礎——面向對象三大特性:封裝、繼承、多態
    繼承的初始化順序初始化父類再初始化子類先執行初始化對象中屬性,再執行構造方法中的初始化。創建本類對象時,調用的方法為本類方法;創建子類對象時,調用的方法為子類重寫的方法或者繼承的方法;使用多態的時候要注意:如果我們在子類中編寫一個獨有的方法(沒有繼承父類的方法),此時就不能通過父類的引用創建的子類對象來調用該方法!!!注意: 繼承是多態的基礎。
  • python | 關於多重繼承那些事
    什麼是多重繼承繼承是面向對象編程的一個重要的方式 ,通過繼承 ,子類就可以擴展父類的功能 。和 c++ 一樣 ,在 python 中一個類能繼承自不止一個父類 ,這叫做 python 的多重繼承(Multiple Inheritance )。多重繼承的語法與單繼承類似 。
  • C 語言面向對象編程 - 繼承
    上一篇文章的具體內容,可以查看以下連結: C 語言面向對象編程 - 封裝本篇文章繼續來討論一下,如何使用 C 語言實現面向對象編程的一個重要特性:繼承。繼承就是基於一個已有的類(一般稱作父類或基類),再去重新聲明或創建一個新的類,這個類可以稱為子類或派生類。子類或派生類可以訪問父類的數據和函數,然後子類裡面又添加了自己的屬性和數據。
  • 前端JS&CSS
    觸發條件:規則:html說一下<label>標籤的用法label標籤主要是方便滑鼠點擊使用,擴大可點擊的範圍,增強用戶操作體驗遍歷A節點的父節點下的所有子節點這題考查原生的js操作dom,屬於非常簡單的基礎題,但長時間使用mvvm框架,可能會忘記<script> var b=document.getElementById
  • 【每日一題】(39題)關於script標籤,你可能不知道的地方?
    •第 28 題:【每日一題】(28題)面試官:原型鏈與構造函數結合方法繼承與原型式繼承的區別?•第 22 題:【每日一題】(22題)面試官問:var與const,let的主要區別是什麼?•第 21 題:【每日一題】(21題)面試官問:談談JS中的 this 的綁定?•第 20 題:【每日一題】(20題)面試官問:談談JS中的 webSockets 的理解?
  • js Date對象常用操作
    一、創建Date()對象var date1 = new Date();// 參數是時間戳var date2 = new Date(1486915200000);// 參數是日期字符串var date3 = new Date('2017/2/13');// 參數是年、月、日...
  • 遺囑繼承什麼情況下會失效?遺產繼承訴訟時效有多久?遺產繼承糾紛處理方式有哪些?
    夫妻一方欠債另一方需要還錢嗎2019房地產項目盡職調查指引最高院指導案例:「以房抵債」適用精解從案例看基於不動產的案外人執行異議之訴金融不良資產案件「 執行難 」 問題研究精粹司法拍賣流拍後,「以房抵債」稅費由誰承擔?
  • Java基礎:封裝與繼承
    如何隱藏類的具體實現和數據?其實之前的代碼中我們已經用到了,現在我們來系統地了解一下。Java是通過訪問控制關鍵字來實現的信息隱藏的,一共有三個關鍵字:public、protected和private。關鍵字可用於修飾類,或者修飾類中的成員變量和成員方法。
  • 在設計原則中,為什麼反覆強調組合要優於繼承?
    面向對象編程中,有一條非常經典的設計原則,那就是:組合優於繼承,多用組合少用繼承。同樣地,在《阿里巴巴Java開發手冊》中有一條規定:謹慎使用繼承的方式進行擴展,優先使用組合的方式實現。為什麼不推薦使用繼承每個人在剛剛學習面向對象編程時都會覺得:繼承可以實現類的復用。所以,很多開發人員在需要復用一些代碼的時候會很自然的使用類的繼承的方式,因為書上就是這麼寫的。繼承是面向對象的四大特性之一,用來表示類之間的is-a關係,可以解決代碼復用的問題。
  • JavaScript高級程序設計(第四版)-面向對象之繼承
    這個例子中實現繼承的關鍵,是 SubType 沒有使用默認原型,而是將其替換成了一個新的對象。這個新的對象恰好是 SuperType 的實例。這樣一來, SubType 的實例不僅能從 SuperType 的實例中繼承屬性和方法,而且還與 SuperType 的原型掛上了鉤。
  • JavaScript——繼承(prototype)
    所有的 JavaScript 對象都會從一個 prototype(原型對象)中繼承屬性和方法。
  • js內置對象
    前言:在本章我們首先學習什麼是對象,對象是一種無須的數據集合,由若干個「鍵值對」(key-value)
  • Python中的繼承
    Python繼承繼承允許我們定義一個類,該類繼承另一個類的所有方法和屬性。父類是從其繼承的類,也稱為基類。