中高階前端開發高頻面試題

龍雨溪發表於2019-05-26

使用setTimeout代替setInterval進行間歇呼叫

var executeTimes = 0;
var intervalTime = 500;
var intervalId = null;

// 放開下面的註釋執行setInterval的Demo
intervalId = setInterval(intervalFun,intervalTime);
// 放開下面的註釋執行setTimeout的Demo
// setTimeout(timeOutFun,intervalTime);

function intervalFun(){
    executeTimes++;
    console.log("doIntervalFun——"+executeTimes);
    if(executeTimes==5){
        clearInterval(intervalId);
    }
}

function timeOutFun(){
    executeTimes++;
    console.log("doTimeOutFun——"+executeTimes);
    if(executeTimes<5){
        setTimeout(arguments.callee,intervalTime);
    }
}
複製程式碼

程式碼比較簡單,我們只是在setTimeout的方法裡面又呼叫了一次setTimeout,就可以達到間歇呼叫的目的。

重點來了,為什麼作者建議我們使用setTimeout代替setInterval呢?setTimeout式的間歇呼叫和傳統的setInterval間歇呼叫有什麼區別呢?

區別在於,setInterval間歇呼叫,是在前一個方法執行前,就開始計時,比如間歇時間是500ms,那麼不管那時候前一個方法是否已經執行完畢,都會把後一個方法放入執行的序列中。這時候就會發生一個問題,假如前一個方法的執行時間超過500ms,加入是1000ms,那麼就意味著,前一個方法執行結束後,後一個方法馬上就會執行,因為此時間歇時間已經超過500ms了。

var executeTimes = 0;
var intervalTime = 500;
var intervalId = null;
var oriTime = new Date().getTime();

// 放開下面的註釋執行setInterval的Demo
// intervalId = setInterval(intervalFun,intervalTime);
// 放開下面的註釋執行setTimeout的Demo
setTimeout(timeOutFun,intervalTime);

function intervalFun(){
    executeTimes++;
    var nowExecuteTimes = executeTimes;
    var timeDiff = new Date().getTime() - oriTime;
    console.log("doIntervalFun——"+nowExecuteTimes+", after " + timeDiff + "ms");
    var delayParam = 0;
    sleep(1000);
    console.log("doIntervalFun——"+nowExecuteTimes+" finish !");
    if(executeTimes==5){
        clearInterval(intervalId);
    }
}

function timeOutFun(){
    executeTimes++;
    var nowExecuteTimes = executeTimes;
    var timeDiff = new Date().getTime() - oriTime;
    console.log("doTimeOutFun——"+nowExecuteTimes+", after " + timeDiff + "ms");
    var delayParam = 0;
    sleep(1000);
    console.log("doTimeOutFun——"+nowExecuteTimes+" finish !");
    if(executeTimes<5){
        setTimeout(arguments.callee,intervalTime);
    }
}

function sleep(sleepTime){
    var start=new Date().getTime();
    while(true){
        if(new Date().getTime()-start>sleepTime){
            break;    
        }
    }
}
複製程式碼

(這裡使用大牛提供的sleep函式來模擬函式執行的時間) 執行setInterval的Demo方法,看控制檯

doIntervalFun——1, after 500ms
VM2854:19 doIntervalFun——1 finish !
VM2854:16 doIntervalFun——2, after 1503ms
VM2854:19 doIntervalFun——2 finish !
VM2854:16 doIntervalFun——3, after 2507ms
VM2854:19 doIntervalFun——3 finish !
VM2854:16 doIntervalFun——4, after 3510ms
VM2854:19 doIntervalFun——4 finish !
VM2854:16 doIntervalFun——5, after 4512ms
VM2854:19 doIntervalFun——5 finish !
複製程式碼

可以發現,fun2和fun1開始的間歇接近1000ms,剛好就是fun1的執行時間,也就意味著fun1執行完後fun2馬上就執行了,和我們間歇呼叫的初衷背道而馳。

我們註釋掉setInterval的Demo方法,放開setTimeout的Demo方法,執行,檢視控制檯

doTimeOutFun——1, after 500ms
VM2621:32 doTimeOutFun——1 finish !
VM2621:29 doTimeOutFun——2, after 2001ms
VM2621:32 doTimeOutFun——2 finish !
VM2621:29 doTimeOutFun——3, after 3503ms
VM2621:32 doTimeOutFun——3 finish !
VM2621:29 doTimeOutFun——4, after 5004ms
VM2621:32 doTimeOutFun——4 finish !
VM2621:29 doTimeOutFun——5, after 6505ms
VM2621:32 doTimeOutFun——5 finish !
複製程式碼

這下終於正常了,fun1和fun2相差了1500ms = 1000 + 500,fun2在fun1執行完的500ms後執行。

閉包

  1. 實現私有變數 如果我們寫一個函式,裡面有一個name值,我們可以允許任何人訪問這個name屬性,但是隻有少部分人,可以修改這個name屬性,我們就可以使用閉包,可以在setName值中,寫哪些人具有修改的許可權。
var person = function(){   
    //變數作用域為函式內部,外部無法訪問,不會與外部變數發生重名衝突   
    var name = "FE";      
    return {
      //管理私有變數   
       getName : function(){   
           return name;   
       },   
       setName : function(newName){   
           name = newName;   
       }   
    }   
}; 
複製程式碼
  1. 資料快取 假如說我們執行一個計算量很大函式,返回一個值,而這個值在其他函式中還有應用,這種情況下使用閉包,可以將該資料儲存在記憶體中,供其他的函式使用(這是在其他部落格中看到的,具體不是很清楚,如果有興趣,可以自己查閱相關文獻)。

缺點: 造成記憶體消耗過大,如果處理不當,會造成記憶體洩漏

陣列中的forEach和map的區別

大多數情況下,我們都要對陣列進行遍歷,然後經常用到的兩個方法就是forEach和map方法。 先來說說它們的共同點

  • 相同點 都是迴圈遍歷陣列中的每一項 forEach和map方法裡每次執行匿名函式都支援3個引數,引數分別是item(當前每一項),index(索引值),arr(原陣列) 匿名函式中的this都是指向window 只能遍歷陣列 都不會改變原陣列
  • 區別 map方法

1.map方法返回一個新的陣列,陣列中的元素為原始陣列呼叫函式處理後的值。 2.map方法不會對空陣列進行檢測,map方法不會改變原始陣列。 3.瀏覽器支援:chrome、Safari1.5+、opera都支援,IE9+,

array.map(function(item,index,arr){},thisValue)

var arr = [0,2,4,6,8];
var str = arr.map(function(item,index,arr){
    console.log(this); //window
    console.log("原陣列arr:",arr); //注意這裡執行5次
    return item/2;
},this);
console.log(str);//[0,1,2,3,4]
複製程式碼

若arr為空陣列,則map方法返回的也是一個空陣列。

forEach方法

  1. forEach方法用來呼叫陣列的每個元素,將元素傳給回撥函式 2.forEach對於空陣列是不會呼叫回撥函式的。
Array.forEach(function(item,index,arr){},this)
var arr = [0,2,4,6,8];
var sum = 0;
var str = arr.forEach(function(item,index,arr){
    sum += item;
    console.log("sum的值為:",sum); //0 2 6 12 20
    console.log(this); //window
},this)
console.log(sum);//20
console.log(str); //undefined
複製程式碼

無論arr是不是空陣列,forEach返回的都是undefined。這個方法只是將陣列中的每一項作為callback的引數執行一次。

for in和for of的區別

遍歷陣列通常使用for迴圈,ES5的話也可以使用forEach,ES5具有遍歷陣列功能的還有map、filter、some、every、reduce、reduceRight等,只不過他們的返回結果不一樣。但是使用foreach遍歷陣列的話,使用break不能中斷迴圈,使用return也不能返回到外層函式。

Array.prototype.method=function(){
&emsp;&emsp;console.log(this.length);
}
var myArray=[1,2,4,5,6,7]
myArray.name="陣列"
for (var index in myArray) {
  console.log(myArray[index]);
}
複製程式碼

使用for in 也可以遍歷陣列,但是會存在以下問題:

  1. index索引為字串型數字,不能直接進行幾何運算

  2. 遍歷順序有可能不是按照實際陣列的內部順序

  3. 使用for in會遍歷陣列所有的可列舉屬性,包括原型。例如上慄的原型方法method和name屬性

所以for in更適合遍歷物件,不要使用for in遍歷陣列。

那麼除了使用for迴圈,如何更簡單的正確的遍歷陣列達到我們的期望呢(即不遍歷method和name),ES6中的for of更勝一籌.

Array.prototype.method=function(){
&emsp;&emsp;console.log(this.length);
}
var myArray=[1,2,4,5,6,7]
myArray.name="陣列";
for (var value of myArray) {
  console.log(value);
}
複製程式碼

記住,for in遍歷的是陣列的索引(即鍵名),而for of遍歷的是陣列元素值。

for of遍歷的只是陣列內的元素,而不包括陣列的原型屬性method和索引name

遍歷物件 通常用for in來遍歷物件的鍵名

Object.prototype.method=function(){
&emsp;&emsp;console.log(this);
}
var myObject={
&emsp;&emsp;a:1,
&emsp;&emsp;b:2,
&emsp;&emsp;c:3
}
for (var key in myObject) {
  console.log(key);
}
複製程式碼

for in 可以遍歷到myObject的原型方法method,如果不想遍歷原型方法和屬性的話,可以在迴圈內部判斷一下,hasOwnPropery方法可以判斷某屬性是否是該物件的例項屬性

for (var key in myObject) {
&emsp;&emsp;if(myObject.hasOwnProperty(key)){
&emsp;&emsp;&emsp;&emsp;console.log(key);
&emsp;&emsp;}
}
複製程式碼

同樣可以通過ES5的Object.keys(myObject)獲取物件的例項屬性組成的陣列,不包括原型方法和屬性。

Object.prototype.method=function(){
&emsp;&emsp;console.log(this);
}
var myObject={
&emsp;&emsp;a:1,
&emsp;&emsp;b:2,
&emsp;&emsp;c:3
}
Object.keys(myObject).forEach(function(key,index){&emsp;&emsp;
    console.log(key,myObject[key])
})
複製程式碼

實現一個EventEmitter方法

EventEmitter 的核心就是事件觸發與事件監聽器功能的封裝。 當你回答出 vue 中用 emit 通訊的時候,就要小心了。EventEmitter 方法主要包含了 on,emit,once,off方法。

class Event {
    constructor() {
          this.events = Object.create(null);
      }
      on(name, fn) {
        if (!this.events[name]) {
            this.events[name] = []
          }
          this.events[name].push(fn);
          return this;
      }
      emit(name, ...args) {
        if (!this.events[name]) {
            return this;
        }
        const fns = this.events[name]
        fns.forEach(fn => fn.call(this, ...args))
        return this;
      }
      off(name,fn) {
        if (!this.events[name]) {
            return this;
        }
          if (!fn) {
            this.events[name] = null
            return this
          }
          const index = this.events[name].indexOf(fn);
          this.events[name].splice(index, 1);
        return this;
      }
      once(name,fn) {
        const only = () => {
          fn.apply(this, arguments);
          this.off(name, only);
        };
        this.on(name, only);
        return this;
      }
}
複製程式碼

let、var、const區別

var 第一個就是作用域的問題,var不是針對一個塊級作用域,而是針對一個函式作用域。舉個例子:

function runTowerExperiment(tower, startTime) {
  var t = startTime;

  tower.on("tick", function () {
    ... code that uses t ...
  });
  ... more code ...
}
複製程式碼

這樣是沒什麼問題的,因為回撥函式中可以訪問到變數t,但是如果我們在回撥函式中再次命名了變數t呢?

function runTowerExperiment(tower, startTime) {
  var t = startTime;

  tower.on("tick", function () {
    ... code that uses t ...
    if (bowlingBall.altitude() <= 0) {
      var t = readTachymeter();
      ...
    }
  });
  ... more code ...
}
複製程式碼

後者就會將前者覆蓋。

第二個就是迴圈的問題。 看下面例子:

var messages = ["Meow!", "I'm a talking cat!", "Callbacks are fun!"];

for (var i = 0; i < messages.length; i++) {
    setTimeout(function () {
        document.write(messages[i]);
    },i*1500);
}
複製程式碼

輸出結果是:undefined 因為for迴圈後,i置為3,所以訪問不到其值。

let 為了解決這些問題,ES6提出了let語法。let可以在{},if,for裡宣告,其用法同var,但是作用域限定在塊級。但是javascript中不是沒有塊級作用域嗎?這個我們等會講。還有一點很重要的就是let定義的變數不存在變數提升。

變數提升 這裡簡單提一下什麼叫做變數提升。

var v='Hello World'; 
(function(){ 
    alert(v); 
    var v='I love you'; 
})()
複製程式碼

上面的程式碼輸出結果為:undefined。

為什麼會這樣呢?這就是因為變數提升,變數提升就是把變數的宣告提升到函式頂部,比如:

(function(){ 
    var a='One'; 
    var b='Two'; 
    var c='Three'; 
})()
複製程式碼

實際上就是:

(function(){ 
    var a,b,c; 
    a='One'; 
    b='Two'; 
    c='Three'; 
})()
複製程式碼

所以我們剛才的例子實際上是:

var v='Hello World'; 
(function(){ 
    var v;
    alert(v); 
    v='I love you'; 
})()
複製程式碼

所以就會返回undefined啦。

這也是var的一個問題,而我們使用let就不會出現這個問題。因為它會報語法錯誤:

{     
    console.log( a );   // undefined
    console.log( b );   // ReferenceError!      
    var a;
    let b;     
}
複製程式碼

再來看看let的塊級作用域。

function getVal(boo) {
    if (boo) {
        var val = 'red'
        // ...
        return val
    } else {
        // 這裡可以訪問 val
        return null
    }
    // 這裡也可以訪問 val
}
複製程式碼

而使用let後:

function getVal(boo) {
    if (boo) {
        let val = 'red'
        // ...
        return val
    } else {
        // 這裡訪問不到 val
        return null
    }
    // 這裡也訪問不到 val
}
複製程式碼

同樣的在for迴圈中:

function func(arr) {
    for (var i = 0; i < arr.length; i++) {
        // i ...
    }
    // 這裡訪問得到i
}
複製程式碼

使用let後:

function func(arr) {
    for (let i = 0; i < arr.length; i++) {
        // i ...
    }
    // 這裡訪問不到i
}
複製程式碼

也就是說,let只能在花括號內部起作用。

const 再來說說const,const代表一個值的常量索引。

const aa = 11;
alert(aa) //11
aa = 22;
alert(aa) //報錯
複製程式碼

但是常量的值在垃圾回收前永遠不能改變,所以需要謹慎使用。

還有一條需要注意的就是和其他語言一樣,常量的宣告必須賦予初值。即使我們想要一個undefined的常量,也需要宣告:

const a = undefined;
複製程式碼

塊級作用域 最後提一下剛才說到的塊級作用域。

在之前,javascript是沒有塊級作用域的,我們都是通過()來模擬塊級作用域。

(function(){
 //這裡是塊級作用域 
})();
複製程式碼

但是在ES6中,{}就可以直接程式碼塊級作用域。所以{}內的內容是不可以在{}外訪問得到的。

我們可以看看如下程式碼:

if (true) {
    function foo() {
        console.log("1" );
    }
}else {
    function foo() {
        console.log("2" );
    }
}

foo();      // 1
複製程式碼

在我們所認識的javascript裡,這段程式碼的輸出結果為1。這個叫做函式宣告提升,不僅僅提升了函式名,也提升了函式的定義。

但是在ES6裡,這段程式碼或丟擲ReferenceErroe錯誤。因為{}的塊級作用域,導致外面訪問不到foo(),也就是說函式宣告和let定義變數一樣,都被限制在塊級作用域中了。

事件迴圈

從promise、process.nextTick、setTimeout出發,談談Event Loop中的Job queue

簡要介紹:談談promise.resove,setTimeout,setImmediate,process.nextTick在EvenLoop佇列中的執行順序

1.問題的引出 event loop都不陌生,是指主執行緒從“任務佇列”中迴圈讀取任務,比如

例1:

setTimeout(function(){console.log(1)},0);

console.log(2)

//輸出2,1
複製程式碼

在上述的例子中,我們明白首先執行主執行緒中的同步任務,當主執行緒任務執行完畢後,再從event loop中讀取任務,因此先輸出2,再輸出1。

event loop讀取任務的先後順序,取決於任務佇列(Job queue)中對於不同任務讀取規則的限定。比如下面一個例子:

例2:

setTimeout(function () {
  console.log(3);
}, 0);

Promise.resolve().then(function () {
  console.log(2);
});
console.log(1);
//輸出為  1  2 3
複製程式碼

先輸出1,沒有問題,因為是同步任務在主執行緒中優先執行,這裡的問題是setTimeout和Promise.then任務的執行優先順序是如何定義的。

2 . Job queue中的執行順序 在Job queue中的佇列分為兩種型別:macro-task和microTask。我們舉例來看執行順序的規定,我們設

macro-task佇列包含任務: a1, a2 , a3 micro-task佇列包含任務: b1, b2 , b3

執行順序為,首先執行marco-task佇列開頭的任務,也就是 a1 任務,執行完畢後,在執行micro-task佇列裡的所有任務,也就是依次執行b1, b2 , b3,執行完後清空micro-task中的任務,接著執行marco-task中的第二個任務,依次迴圈。

瞭解完了macro-task和micro-task兩種佇列的執行順序之後,我們接著來看,真實場景下這兩種型別的佇列裡真正包含的任務(我們以node V8引擎為例),在node V8中,這兩種型別的真實任務順序如下所示:

macro-task(巨集任務)佇列真實包含任務: script(主程式程式碼),setTimeout, setInterval, setImmediate, I/O, UI rendering

micro-task(微任務)佇列真實包含任務: process.nextTick, Promises, Object.observe, MutationObserver

由此我們得到的執行順序應該為:

script(主程式程式碼)—>process.nextTick—>Promises…——>setTimeout——>setInterval——>setImmediate——> I/O——>UI rendering

在ES6中macro-task佇列又稱為ScriptJobs,而micro-task又稱PromiseJobs

3 . 真實環境中執行順序的舉例

(1) setTimeout和promise

例3:

setTimeout(function () {
  console.log(3);
}, 0);

Promise.resolve().then(function () {
  console.log(2);
});

console.log(1);
複製程式碼

我們先以第1小節的例子為例,這裡遵循的順序為:

script(主程式程式碼)——>promise——>setTimeout 對應的輸出依次為:1 ——>2————>3 (2) process.nextTick和promise、setTimeout

例子4:

setTimeout(function(){console.log(1)},0);

new Promise(function(resolve,reject){
   console.log(2);
   resolve();
}).then(function(){console.log(3)
}).then(function(){console.log(4)});

process.nextTick(function(){console.log(5)});

console.log(6);
//輸出2,6,5,3,4,1
複製程式碼

這個例子就比較複雜了,這裡要注意的一點在定義promise的時候,promise構造部分是同步執行的,這樣問題就迎刃而解了。

首先分析Job queue的執行順序:

script(主程式程式碼)——>process.nextTick——>promise——>setTimeout

I) 主體部分: 定義promise的構造部分是同步的, 因此先輸出2 ,主體部分再輸出6(同步情況下,就是嚴格按照定義的先後順序)

II)process.nextTick: 輸出5

III)promise: 這裡的promise部分,嚴格的說其實是promise.then部分,輸出的是3,4

IV) setTimeout : 最後輸出1

綜合的執行順序就是: 2——>6——>5——>3——>4——>1

(3)更復雜的例子

setTimeout(function(){console.log(1)},0);

new Promise(function(resolve,reject){
   console.log(2);
   setTimeout(function(){resolve()},0)
}).then(function(){console.log(3)
}).then(function(){console.log(4)});

process.nextTick(function(){console.log(5)});

console.log(6);

//輸出的是  2 6 5 1 3 4
複製程式碼

這種情況跟我們(2)中的例子,區別在於promise的構造中,沒有同步的resolve,因此promise.then在當前的執行佇列中是不存在的,只有promise從pending轉移到resolve,才會有then方法,而這個resolve是在一個setTimout時間中完成的,因此3,4最後輸出。

typeof和instanceof

ECMAScript是鬆散型別的,一次需要一種手段來檢測給定變數的資料型別,typeof操作符(注意不是函式哈!)就是負責提供這方面資訊的

typeof 可以用於檢測基本資料型別和引用資料型別。

語法格式如下:

typeof variable
複製程式碼

返回6種String型別的結果:

  • "undefined" - 如果這個值未定義
  • "boolean" - 如果這個值是布林值
  • "string" - 如果這個值是字串
  • "number" - 如果這個值是數值
  • "object" - 如果這個值是物件或null
  • "function" - 如果這個值是函式 示例:
console.log(typeof 'hello'); // "string"
console.log(typeof null); // "object"
console.log(typeof (new Object())); // "object"
console.log(typeof(function(){})); // "function"
複製程式碼

typeof主要用於檢測基本資料型別:數值、字串、布林值、undefined, 因為typeof用於檢測引用型別值時,對於任何Object物件例項(包括null),typeof都返回"object"值,沒辦法區分是那種物件,對實際編碼用處不大。

instanceof 用於判斷一個變數是否某個物件的例項

在檢測基本資料型別時typeof是非常得力的助手,但在檢測引用型別的值時,這個操作符的用處不大,通常,我們並不是想知道某個值是物件,而是想知道它是什麼型別的物件。此時我們可以使用ECMAScript提供的instanceof操作符。

語法格式如下:

result = variable instanceof constructor
複製程式碼

返回布林型別值:

  • true - 如果變數(variable)是給定引用型別的例項,那麼instanceof操作符會返回true
  • false - 如果變數(variable)不是給定引用型別的例項,那麼instanceof操作符會返回false 示例:
function Person(){}
function Animal(){}
var person1 = new Person();
var animal1 = new Animal();
console.log(person1 instanceof Person); // true
console.log(animal1 instanceof Person); // false
console.log(animal1 instanceof Object); // true
console.log(1 instanceof Person);   //false


var oStr =  new String("hello world");
console.log(typeof(oStr));  	// object
console.log(oStr instanceof String);
console.log(oStr instanceof Object);

// 判斷 foo 是否是 Foo 類的例項

function Foo(){}
var foo = new Foo();
console.log(foo instanceof Foo);

// instanceof 在繼承中關係中的用法
console.log('instanceof 在繼承中關係中的用法');


function Aoo(){}
function Foo(){}

Foo.prototype = new Aoo();
var fo = new Foo();

console.log(fo instanceof Foo);
console.log(fo instanceof Aoo)
複製程式碼

根據規定,所有引用型別的值都是Object的例項。因此,在檢測一個引用型別值和Object建構函式時,instanceof操作符會始終返回true。如果使用instanceof 操作符檢測基本型別值時,該操作符會始終返回false,因為基本型別不是物件。

console.log(Object.prototype.toString.call(null));
// [object Null]
undefined
console.log(Object.prototype.toString.call([1,2,3]));
//[object Array]
undefined
console.log(Object.prototype.toString.call({}));
// [object Object]
複製程式碼

常見的繼承的幾種方法

原型鏈繼承

定義 利用原型讓一個引用型別繼承另外一個引用型別的屬性和方法 程式碼

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;
}

var instance = new SubType();
alert(instance.getSuperValue());//true
複製程式碼
  • 優點 簡單明瞭,容易實現,在父類新增原型屬性和方法,子類都能訪問到。
  • 缺點 包含引用型別值的函式,所有的例項都指向同一個引用地址,修改一個,其他都會改變。不能像超型別的建構函式傳遞引數

建構函式繼承 定義 在子型別建構函式的內部呼叫超型別的建構函式

程式碼

function SuperType(){
    this.colors = ['red','yellow'];
}

function SubType(){
    SuperType.call(this);
}

var instance1 = new SubType();
instance1.colors.push('black');


var instance2 = new SubType();
instance2.colors.push('white');

alert(instance1.colors);//'red','yellow','black'

alert(instance2.colors);//'red','yellow','white'
複製程式碼
  • 優點 簡單明瞭,直接繼承了超型別建構函式的屬性和方法
  • 缺點 方法都在建構函式中定義,因此函式複用就無從談起了,而且超型別中的原型的屬性和方法,對子型別也是不可見的,結果所有的型別只能使用建構函式模式。

組合繼承

定義 使用原型鏈實現多原型屬性和方法的繼承,使用建構函式實現例項的繼承

程式碼

function SuperType(name){
    this.name = name;
    this.colors = ['red','black'];
}

SuperType.prototype.sayName = function()
{
   alert(this.name); 
}


function SubType(name,age){
    SuperType.call(this,name);
    this.age = age;
}

SubType.protptype = new SuperType();
SubType.protptype.sayAge = function(){
    alert(this.age);
    
}
複製程式碼
  • 優點 解決了建構函式和原型繼承中的兩個問題
  • 缺點 無論什麼時候,都會呼叫兩次超型別的建構函式

公眾號

相關文章