InterviewMap —— Javascript (三)

1、深刻理解 javascript 的 this 綁定

this問題的由來javascript

var people = {
    Name: "海洋餅乾",
    getName : function(){
        console.log(this.Name);
    }
};
var bar = people.getName;

bar();    // undefined
複製代碼
var people = {
    Name: "海洋餅乾",
    getName : function(){
        console.log(this.Name);
    }
};
var bar = people.getName;
var Name = "zjj";
bar(); // zjj
複製代碼

上面兩個問題,分別獲得的記過是 undefinedzjj。是否是以爲很彆扭,下面咱們就去溫習一下this的綁定規則吧。html

一、this出現的意義

內存的數據結構java

var obj = {foo: 5}
複製代碼

原始的對象以字典結構保存,每個屬性名都對應一個屬性描述對象。舉例來講,上面例子的foo屬性,其實是如下面的形式保存的。web

函數面試

因爲函數是一個單獨的值,因此但是在不一樣的環境中調用它。數組

var f = function () {};
var obj = { f: f };

// 單獨執行,全局下
f()

// obj 環境下執行
obj.f()
複製代碼

因爲函數能夠在不一樣的環境中執行,因此須要一種機制能夠,在函數內部訪問到當前的運行環境。因此this就出現了。bash

以下面的栗子。函數可使用不一樣的運行環境,致使內部使用的x的值是變化的。數據結構

var f = function () {
  console.log(this.x);
}

var x = 1;
var obj = {
  f: f,
  x: 2,
};

// 單獨執行
f() // 1

// obj 環境執行
obj.f() // 2
複製代碼

因此這就是this存在的意義了。閉包

二、this綁定規則

牢記準則: this指向什麼,徹底取決於什麼地方以什麼方式調用,而不是建立時。app

4種綁定的規則以下:

(1)、默認綁定

function foo(){
    var a = 1 ;
    console.log(this.a);    // 10
    // window.a
}
var a = 10;
foo(); // 默認是window
複製代碼

(2)、隱性綁定

function foo(){
    console.log(this.a);
}

var obj = {
    a : 10,
    foo : foo  // 必須有這條
}

foo();                // undefined

obj.foo();            // 10
複製代碼

這裏的obj.foo()就是隱性綁定了。若是是鏈性的關係,好比 xx.yy.obj.foo();, 上下文取函數的直接上級,即緊挨着的那個,或者說對象鏈的最後一個(也便是obj了)。

(3)、顯性綁定

隱性質綁定的限制: 隱性綁定中有一個致命的限制,就是上下文必須包含咱們的函數 ,例:var obj = { foo : foo },若是上下文不包含咱們的函數用隱性綁定明顯是要出錯的。

顯性綁定,js 給咱們提供的函數 callapply,它們的做用都是改變函數的this指向,第一個參數都是 設置this對象。

使用call和apply

function foo(a,b){
    console.log(a+b);
}
foo.call(null,'海洋','餅乾');        // 海洋餅乾 這裏this指向不重要就寫null了
foo.apply(null, ['海洋','餅乾'] );     // 海洋餅乾
複製代碼

使用bind

var obj = {
    a: 10
}

function foo () {
    console.log(this.a);
}

foo = foo.bind(obj);

foo();  // 10
複製代碼

修改上面的那個栗子:

function f (){
    cosnole,log(this.a);
};

var obj = {
    a: 11
}

f.call(obj);

f(); // 11
複製代碼

這樣顯然沒有了上面所說的隱性的弊端了。

(4)、new 綁定

function foo(){
    this.a = 10;
    console.log(this);
}
foo();                    // window對象
console.log(window.a);    // 10 默認綁定

var obj = new foo();      // foo{ a : 10 } 建立的新對象的默認名爲函數名
                          // 而後等價於 foo { a : 10 }; var obj = foo;
console.log(obj.a);       // 10 new綁定
複製代碼

函數與new一塊使用即構造函數,this指向新建立的對象

總結

  • 若是函數被new 修飾
this綁定的是新建立的對象,例:var bar = new foo(); 函數 foo 中的 this 就是一個叫foo的新建立的對象 , 而後將這個對象賦給bar , 這樣的綁定方式叫 new綁定 .
複製代碼
  • 若是函數是使用call,apply,bind來調用的
this綁定的是 call,apply,bind 的第一個參數.例: foo.call(obj); , foo 中的 this 就是 obj , 這樣的綁定方式叫 顯性綁定 .
複製代碼
  • 若是函數是在某個 上下文對象 下被調用
this綁定的是那個上下文對象,例 : var obj = { foo : foo }; obj.foo(); foo 中的 this 就是 obj . 這樣的綁定方式叫 隱性綁定 .
複製代碼
  • 若是都不是,即便用默認綁定
例:function foo(){...} foo() ,foo 中的 this 就是 window.(嚴格模式下默認綁定到undefined).
   這樣的綁定方式叫 默認綁定 .
複製代碼

三、箭頭函數中的this

function a() {
    return () => {
        return () => {
        	console.log(this)
        }
    }
}
console.log(a()()())
複製代碼

箭頭函數實際上是沒有 this 的,這個函數中的 this 只取決於他外面的第一個不是箭頭函數的函數的 this。在這個例子中,由於調用 a 符合前面代碼中的第一個狀況,因此 this 是 window。而且 this 一旦綁定了上下文,就不會被任何代碼改變。

2、閉包

一、認識閉包

閉包的定義: 閉包是指那些能夠訪問自由變量的函數。

自由變量: 指能夠在函數中訪問,但並非函數參數也不是局部變量的變量

閉包 = 函數 + 自由變量

var a = 1;

function foo() {
    console.log(a);
}

foo();
複製代碼

上訴栗子也知足上面的要求,因此他也是一個閉包。但是你會發現,他怎麼和咱們平時所見到的不同呢?

在《JavaScript權威指南》中就講到:從技術的角度講,全部的JavaScript函數都是閉包。

其實這是理論上的閉包,並非實踐中的閉包,咱們平時碰見的都是實踐中的閉包。

ECMAScript中,閉包指的是:

- 理論上: 全部的函數都是閉包。由於每個函數建立的時候都會保存上層上下文中的變量對象保存起來。存放到做用域鏈中,即使是全局變量也是。

- 實踐上: 只有知足下列條件的纔是:
    即使建立他的執行上下文已經被銷燬了,可是他仍然存在
    在代碼中引用了自由變量
複製代碼

咱們先從栗子入手吧:

var scope = "global scope";
function checkscope(){
    var scope = "local scope"; // 內部變量
    
    function f(){
        // 引用自由變量 
        return scope;
    }
    // 返回出去,表示仍然存在
    return f;
}

var foo = checkscope();
foo();
複製代碼

首先咱們要分析一下這段代碼中執行上下文棧和執行上下文的變化狀況。

第一步 建立全局執行上下文並壓入執行上下文棧

ECStack = [
    globalContext
];
複製代碼

第二步 全局執行上下文對象

globalContext.VO = {
    scope: undefined,
    checkscope: function(){},
    foo: undefined
}
複製代碼

第三步 全局執行,建立checkscope函數

globalContext.VO = {
    scope: global scope,
    checkscope: function(){},
    foo: undefined
}

checkscope.[[scope]] = {
    globalContext.VO
}
複製代碼

第三步 建立函數上下文,並壓入執行上下文棧

// 函數執行上下文建立好後,壓入執行上下文棧中
ECStack = [
    checkscopeContext,
    globalContext
];
複製代碼

第四步 函數並不會立馬執行,首先是建立函數的scope

checkscopeContext.scope = checkscope.[[scope]]
複製代碼

第五步 建立活動對象AO

checkscopeContext.AO = {
    arguments: {
        length: 0
    },
    scope: undefined
}
複製代碼

第六步 將AO插入到做用域鏈中

checkscopeContext = {
    AO = {
        arguments: {
            length: 0
        },
        scope: undefined,
        f: function(){}
    },
    scope: [AO, checkscope.[[scope]]]
}
複製代碼

第七步 開始執行checkscope函數

checkscopeContext = {
    AO = {
        arguments: {
            length: 0
        },
        scope: 'local scope',
        f: function(){}
    },
    scope: [AO, checkscope.[[scope]]]
}


複製代碼

第八步 checkscope 函數執行完畢,checkscope 執行上下文從執行上下文棧中彈出

ECStack = [
    globalContext
];
複製代碼

第九步 繼續執行全局上下文的代碼,開始建立和執行f函數,首先建立函數上下文,插入到執行上下文棧中。

f.[[scope]] = checkscopeContext.AO

ECStack = [
    fContext,
    globalContext
];


複製代碼

第十步 開始執行f函數,可是並非立馬執行,首先建立AO對象和scope

fContext.AO = {
    arguments: {
        length: 0
    },
    scope: [AO, checkscopeContext.AO, globalContext.VO]
}
複製代碼

第十步 開始真正執行f函數,返回一個值,執行完畢以後,將該函數彈出棧

ECStack = [
    globalContext
];
複製代碼

瞭解到這個過程,咱們應該思考一個問題,那就是:

當 f 函數執行的時候,checkscope 函數上下文已經被銷燬了啊(即從執行上下文棧中被彈出),怎麼還會讀取到 checkscope 做用域下的 scope 值呢?
複製代碼

js是能夠的,由於在f的scope中,Scope: [AO, checkscopeContext.AO, globalContext.VO],。維護了一個做用域鏈。雖說checkscope函數已經被銷燬了,可是因爲f維護本身的做用域鏈,而且使用了上一層的自由變量,即便已經被銷燬,可是checkscopeContext.AO對象卻會駐留在內存中,因此天然咱們的f就能訪問上層變量了。

二、面試必刷題

var data = [];

for (var i = 0; i < 3; i++) {
  data[i] = function () {
    console.log(i);
  };
}

data[0](); // 3
data[1](); // 3
data[2](); // 3
複製代碼

緣由:

全局上下文VO對象爲:

VO = {
    data: [...],
    i: 3
}
複製代碼

當執行 data[0] 函數的時候,data[0] 函數的做用域鏈爲:

data[0]Context = {
    Scope: [AO, globalContext.VO]
}
複製代碼

此時訪問i的話,訪問的都是全局中的,所以都是輸出3.

因此讓咱們改爲閉包看:

var data = [];

for (var i = 0; i < 3; i++) {
  data[i] = (function (i) {
    return function() {
       console.log(i); 
    }
  })(i);
}

data[0](); // 0
data[1](); // 1
data[2](); // 2
複製代碼

此時data[0]的做用域就發生了變化了:

data[0]Context = {
    Scope: [AO, 匿名函數Context.AO ,globalContext.VO]
}
複製代碼

匿名函數中的AO爲:

AO: {
    arguments: {
        0: 0,
        length: 1
    },
    i: 0
}
複製代碼

data[0]Context 的 AO 並無 i 值,因此會沿着做用域鏈從匿名函數 Context.AO 中查找,這時候就會找 i 爲 0,找到了就不會往 globalContext.VO 中查找了,即便 globalContext.VO 也有 i 的值(值爲3),因此打印的結果就是0。

三、如何建立閉包

建立閉包最多見方式,就是在一個函數內部建立另外一個函數,並返回。

function foo (){
    var a = 10;
    
    function bar(){
        return a;
    }
    
    return bar;
}

var result = foo();
result(); // 10
複製代碼

四、閉包的好處

一、 累加 【減小全局變量個數】

function add () {
  var a  = 0;
  return function () {
    a++;
console.log(a);
  }
}

var result = add();
result(); // 1
result(); // 2
複製代碼

二、封裝

var http = function () {
  // 局部變量
  var host = "http://..";

  // 局部變量
  var admin = function(){
    // 局部
    var url = host + "/login"
    return {
      login : function() {
        return 1;
      }
    }
  }();

  return {
    admin
  }
}();

http.admin.login();
複製代碼

五、閉包的使用注意

一、 對捕獲的變量只是個引用,不是複製

二、每調用一次父函數,就會產生一個新的閉包

function f() {
  var num = 1;
  return function () {
    num++;
    alert(num);
  }
}

var result1 = f();
result1(); // 2
result1(); // 3

var result2 = f();
result2(); // 2
result2(); // 3
複製代碼

三、循環

<ul>
    <li id="1">1</li>
    <li id="2">2</li>
    <li id="3">3</li>
  </ul>
  <script>
    for(var i = 1 ; i <= 3; i++) {
      var el = document.getElementById(i);
      el.onclick = function() {
        alert(i);
      }
    }
  </script>

  // 結果是不管點擊那個,都是彈出4
複製代碼
// 解決辦法
<ul>
    <li id="1">1</li>
    <li id="2">2</li>
    <li id="3">3</li>
  </ul>
  <script>
    for(var i = 1 ; i <= 3; i++) {
      var el = document.getElementById(i);
      el.onclick = (function(id) {
        return function () {
          alert(id);
        }
      })(i);;
    }
  </script>
複製代碼

高效使用 JavaScript 閉包

3、js參數按值傳遞

在《JavaScript高級程序設計》第三版 4.1.3,講到傳遞參數:

ECMAScript中全部函數的參數都是按值傳遞的。

按值傳遞: 把外面的值,複製一份傳給函數做爲參數使用。就和把值從一個變量複製到另外一個變量同樣。其實就是創建了一份備份。

js中有基本類型和引用類型。基本類型是存在與棧中的,引用類型是存在於堆中的,可是它的引用地址倒是存在棧中的。

一、按值傳遞

var value = 1;
function foo(v) {
    v = 2;
    console.log(v); //2
}
foo(value);
console.log(value) // 1
複製代碼

很好理解,當傳遞 value 到函數 foo 中,至關於拷貝了一份 value,假設拷貝的這份叫 _value,函數中修改的都是 _value 的值,而不會影響原來的 value 值。

參照上圖來分析:

_value 是 value 的一個備份,或者說是副本,就至關於 _value = value 。此時 _value = 2; 改變了,可是 value 並無變。

二、引用傳遞

var obj = {
    value: 1
};
function foo(o) {
    o.value = 2;
    console.log(o.value); //2
}
foo(obj);
console.log(obj.value) // 2
複製代碼

所謂按引用傳遞,就是傳遞對象的引用,函數內部對參數的任何改變都會影響該對象的值,由於二者引用的是同一個對象。

針對官方的解釋咱們來分析:

ECMAScript中全部函數的參數都是按值傳遞的。

也就是說,把函數外部的值複製給函數內部的參數,就和把值從一個變量複製到另外一個變量同樣。基本類型的傳遞如同基本類型的複製同樣,而引用類型值的傳遞,如同引用類型變量的複製同樣。
複製代碼
  • 總結

很簡單,javascript函數參數都是按值傳遞(都是棧內數據的拷貝)。 基本類型傳的是值自己(由於直接把值存在棧內),引用類型傳的是對象在內存裏面的地址 (由於複雜對象存在堆內,因此在棧裏存對象所在的堆地址)。 這種引用類型的傳遞其實也是傳遞的值,就是堆地址的引用。

4、call和apply的模擬實現

一、call和apply的區別

callapply都是爲了解決改變 this 的指向。做用都是相同的,只是傳參的方式不一樣。

除了第一個參數外,call 能夠接收一個參數列表,apply 只接受一個參數數組。

let a = {
    value: 1
}
function getValue(name, age) {
    console.log(name)
    console.log(age)
    console.log(this.value)
}

getValue.call(a, 'zjj', 12);
getValue.apply(a, ['zjj', 12]);
複製代碼

二、模擬實現 call 和 apply

【1】模擬實現 call

第一步

var foo = {
    value: 1,
    bar: function() {
        console.log(this.value)
    }
};

foo.bar(); // 1
複製代碼

若是咱們設計成這樣子,就能夠實現了。可是咱們爲foo對象又添加了一個屬性有點很差呢,不要緊,咱們能夠delete嘛。

因此咱們模擬的步驟能夠分爲:

將函數設爲對象的屬性
執行該函數
刪除該函數
複製代碼

測試案例:

var obj = {
    a: 1
}

function foo(){
    console.log(this.a);
}

foo.call_new(obj);
複製代碼

實現:

// 初版
Function.prototype.call_new =  function(context){
    // 首先要獲取調用call的函數,用this能夠獲取
    context.fn = this;
    context.fn();
    delete context.fn;
}
複製代碼

第二步

call 函數還能給定參數執行函數。舉個例子:

var obj = {
    a: 1
}

function foo(name){
    console.log(name);  // zjj
    console.log(this.a) // 1
};

foo.call(obj, 'zjj');
複製代碼
Function.prototype.call_new = function (context) {
      // 首先要獲取調用call的函數,用this能夠獲取
      context.fn = this;
      var args = [];
      for (var i = 1; i < arguments.length; i++) {
        args.push('arguments[' + i + ']');
      }
      eval('context.fn(' + args + ')');
      delete context.fn;
    }

    var obj = {
      a: 1
    }

    function foo(name) {
      console.log(name);
      console.log(this.a);
    }

    foo.call_new(obj, 'zjj');
複製代碼

第三步

還有小問題須要解決,this爲null的時候,默認是window。並且函數可能會有返回值的。

Function.prototype.call_new = function (context) {
      var context = context || window;
      // 首先要獲取調用call的函數,用this能夠獲取
      context.fn = this;
      var args = [];
      for (var i = 1; i < arguments.length; i++) {
        args.push('arguments[' + i + ']');
      }
      var result = eval('context.fn(' + args + ')');
      delete context.fn;
      return result;
    }

    var obj = {
      a: 1
    }

    function foo(name) {
      console.log(name);
      console.log(this.a);
      return {
        name: name,
        value: this.a
      }
    }

    console.log(foo.call_new(obj, 'zjj'));
複製代碼
// ES6
Function.prototype.call_new = function (context) {
      var context = context || window;
      // 首先要獲取調用call的函數,用this能夠獲取
      context.fn = this;
      var args = [];
      args = [...arguments].slice(1);
      var result = context.fn(...args);
      delete context.fn;
      return result;
    }
複製代碼

【2】模擬實現 apply

Function.prototype.call_new = function (context, arr) {
      var context = Object(context) || window;
      // 首先要獲取調用call的函數,用this能夠獲取
      context.fn = this;
      let result;
      if (!arr) {
        result = context.fn();
      } else {
        var args = [];
        for (var i = 0; i < arr.length; i++) {
          args.push('arr['+i+']') ;
        }
        // 這裏會自動調用toString方法
        result = eval('context.fn(' + args + ')');
      }

      delete context.fn;
      return result;
    }

    var obj = {
      a: 1
    }

    function foo(name) {
      console.log(name);
      console.log(this.a);
      return {
        name: name,
        value: this.a
      }
    }

    console.log(foo.call_new(obj, ['zjj']));
複製代碼
// 使用 ES6
Function.prototype.call_new = function (context, arr) {
      var context = Object(context) || window;
      // 首先要獲取調用call的函數,用this能夠獲取
      context.fn = this;
      let result;
      if (!arr) {
        result = context.fn();
      } else {
        result = context.fn(...arr);
      }

      delete context.fn;
      return result;
    }
複製代碼

【2】模擬實現 bind

第一步

Function.prototype.bind1 = function (context) {
      var self = this;
      return function () {
        return self.apply(context)
      }
    }

    var obj = {
      a: 1
    }

    function foo() {
      console.log(this.a);
    }
    var result = foo.bind(obj);
    result(); // 1
複製代碼

第二步

處理參數的傳遞

Function.prototype.bind1 = function (context) {
        var self = this;
        var args = Array.prototype.slice.call(arguments, 1); // 獲得參數
        return function () {
          var args1 = Array.prototype.slice.call(arguments);

          return self.apply(context, args.concat(args1));
        }
      }

      var obj = {
        a: 1
      }

      function foo(name, age) {
        console.log(name);
        console.log(age);
        console.log(this.a);
      }
      var result = foo.bind(obj, 'zjj');
      result(18);
複製代碼

5、數組對象和arguments

一、認識類數組對象

var array = [1,2,3,4,5];

var arrayLike = {
    0: '1',
    1: 4,
    2: 'zjj',
    length: 3
}
複製代碼

擁有一個 length 屬性和若干索引屬性的對象

那讓咱們從讀寫、獲取長度、遍歷三個方面看看這兩個對象。

讀寫

console.log(array[0]); // 1
  console.log(arrayLike[0]); // 1

  array[0] = "lll";
  arrayLike[0] = "mmm";

  console.log(array[0]); // lll
  console.log(arrayLike[0]); // mmm
複製代碼

獲取長度

console.log(array.length); // 5
  console.log(arrayLike.length); // 3
複製代碼

遍歷:

for (var i = 0, len = array.length; i < len; i++) {
    console.log(array[i]);
  }
  for (var i = 0, len = arrayLike.length; i < len; i++) {
    console.log(arrayLike[i]);
  }
複製代碼

就上面的狀況,二者是很是的像。

二、類數組對象調用數組的方法

沒法直接調用,只能是Function.call間接調用。

var arrayLike = {0: 'name', 1: 'age', 2: 'sex', length: 3 }

Array.prototype.join.call(arrayLike, '&'); // name&age&sex

Array.prototype.slice.call(arrayLike, 0); // ["name", "age", "sex"] 
// slice能夠作到類數組轉數組

Array.prototype.map.call(arrayLike, function(item){
    return item.toUpperCase();
}); 
// ["NAME", "AGE", "SEX"]
複製代碼

三、類數組轉數組

var arrayLike = {0: 'name', 1: 'age', 2: 'sex', length: 3 }
// 1. slice
Array.prototype.slice.call(arrayLike); // ["name", "age", "sex"] 
// 2. splice
Array.prototype.splice.call(arrayLike, 0); // ["name", "age", "sex"] 
// 3. ES6 Array.from
Array.from(arrayLike); // ["name", "age", "sex"] 
// 4. apply
Array.prototype.concat.apply([], arrayLike)
複製代碼

要說到類數組對象,Arguments 對象就是一個類數組對象。在客戶端 JavaScript中,一些DOM方法(document.getElementsByTagName()等)也返回類數組對象。

四、arguments對象

arguments對象只存在函數體中,包括了參數和其餘的屬性。箭頭函數並無arguments對象。

function foo(name, age, sex) {
    console.log(arguments);
}

foo('name', 'age', 'sex')
複製代碼

arguments 和對應參數的綁定

function foo(name, age, sex, hobbit) {

    console.log(name, arguments[0]); // name name

    // 改變形參
    name = 'new name';

    console.log(name, arguments[0]); // new name new name

    // 改變arguments
    arguments[1] = 'new age';

    console.log(age, arguments[1]); // new age new age

    // 測試未傳入的是否會綁定
    console.log(sex); // undefined

    sex = 'new sex';

    console.log(sex, arguments[2]); // new sex undefined

    arguments[3] = 'new hobbit';

    console.log(hobbit, arguments[3]); // undefined new hobbit

}

foo('name', 'age')
複製代碼

總之:傳入的參數,實參和 arguments 的值會共享,當沒有傳入時,實參與 arguments 值不會共享

除此以外,以上是在非嚴格模式下,若是是在嚴格模式下,實參和 arguments 是不會共享的。

arguments傳遞參數給別的函數

// 使用 apply 將 foo 的參數傳遞給 bar
function foo() {
    bar.apply(this, arguments);
}
function bar(a, b, c) {
   console.log(a, b, c);
}

foo(1, 2, 3)
複製代碼

強大的ES6

function func(...arguments) {
    console.log(arguments); // [1, 2, 3]
}

func(1, 2, 3);
複製代碼

使用ES6的 ... 運算符,咱們能夠輕鬆轉成數組。

累數組對象的應用

若是要總結這些場景的話,暫時能想到的包括:

參數不定長
函數柯里化
遞歸調用
函數重載
複製代碼

6、建立對象的多種方式以及優缺點

建立對象的不一樣方式:

一、工廠模式

function createObj (name, age){
        var o = {};
        o.name = name;
        o.age = age;
        o.getName = function(){
          return this.name;
        }
        return 0;
      }

      createObj('zjj');
複製代碼

缺點:對象沒法識別,由於全部的實例都指向一個原型

二、構造函數模式

function Person(name) {
    this.name = name;
    this.getName = function () {
        console.log(this.name);
    };
}

var person1 = new Person('kevin');
複製代碼

缺點:每次建立實例時,每一個方法都要被建立一次

三、原型模式

function Person(name) {

}

Person.prototype.name = 'keivn';
Person.prototype.getName = function () {
    console.log(this.name);
};

var person1 = new Person();
複製代碼

優勢:解決了每一個實例的建立都要從新建立一遍方法的缺點 缺點:可是1. 全部的屬性和方法都共享 2. 不能初始化參數

四、上面原型模式的改進

function Person(name) {

}

Person.prototype = {
    constructor: Person,
    name: 'kevin',
    getName: function () {
        console.log(this.name);
    }
};

var person1 = new Person();
複製代碼

優勢: constructor有了 缺點: 屬性都共享,很差初始化

五、組合模式【推薦使用】

function Person(name) {
    this.name = name;
}

Person.prototype = {
    constructor: Person,
    getName: function () {
        console.log(this.name);
    }
};

var person1 = new Person();
複製代碼

六、 寄生構造函數模式【特殊使用】

function Person(name) {

    var o = new Object();
    o.name = name;
    o.getName = function () {
        console.log(this.name);
    };

    return o;

}

var person1 = new Person('kevin');
console.log(person1 instanceof Person) // false
console.log(person1 instanceof Object)  // true
複製代碼

這樣方法能夠在特殊狀況下使用。好比咱們想建立一個具備額外方法的特殊數組,可是又不想直接修改Array構造函數,咱們能夠這樣寫:

function SpecialArray() {
    var values = new Array();

    for (var i = 0, len = arguments.length; i < len; i++) {
        values.push(arguments[i]);
    }

    values.toPipedString = function () {
        return this.join("|");
    };
    return values;
}

var colors = new SpecialArray('red', 'blue', 'green');
var colors2 = SpecialArray('red2', 'blue2', 'green2');


console.log(colors);
console.log(colors.toPipedString()); // red|blue|green

console.log(colors2);
console.log(colors2.toPipedString()); // red2|blue2|green2
複製代碼

7、繼承的方式

一、原型鏈繼承

function Parent(name) {
    this.name = name || 'kiven';
}

Parent.prototype.getName = function () {
    return this.name;
}

function Child(){

}

// 這裏是原型鏈繼承的標誌:
Child.prototype = new Parent('zzz');

var c = new Child();
console.log(c.getName());// zzz
複製代碼

存在的問題:

1、 不能向父級傳值。
2、 引用類型的實例被多個實例共享,以下:

function Parent () {
    this.names = ['kevin', 'daisy'];
}

function Child () {

}

Child.prototype = new Parent();

var child1 = new Child();

child1.names.push('yayu');

console.log(child1.names); // ["kevin", "daisy", "yayu"]

var child2 = new Child();

console.log(child2.names); // ["kevin", "daisy", "yayu"]
複製代碼

直接將父類的實例new Parent()賦給了子類的原型,其實子類的實例child1,child2自己是一個徹底空的對象,全部的屬性和方法都得去原型鏈上去找,於是找到的屬性方法都是同一個。

二、構造函數模式

function Person (name, age) {
    this.name = name;
    this.age = age;
}
Person.prototype.say = function(){
    console.log('hello, my name is ' + this.name);
};
function Man(name, age) {
    Person.apply(this, arguments);
}
//Man.prototype = new Person('pursue');
var man1 = new Man('joe');
var man2 = new Man('david');
console.log(man1.name === man2.name);//false
man1.say(); //say is not a function
複製代碼

這裏子類的在構造函數裏利用了apply去調用父類的構造函數,從而達到繼承父類屬性的效果,比直接利用原型鏈要好的多,至少每一個實例都有本身那一份資源,可是這種辦法只能繼承父類的實例屬性,於是找不到say方法,爲了繼承父類全部的屬性和方法,則就要修改原型鏈,從而引入了組合繼承方式。

三、組合繼承模式

function Parent(name, age) {
    this.name = name || 'kiven';
    this.age = age;
}

Parent.prototype.getName = function () {
    return this.name;
}

function Child(name){
    Parent.call(this, name);
    // 父類綁定 this 
}

// 子類原型鏈繼承
Child.prototype = new Parent();
Child.prototype.constructor = Child;

var c = new Child('kkk');
c.age = 10;
console.log(c.name);
console.log(c.age);
var c1 = new Child('llll');
console.log(c1.name);
console.log(c1.age);


// 調用父類的方法
console.log(c1.getName());
複製代碼

組合模式是 JavaScript 中最經常使用的繼承模式。

function Person (name, age) {
    this.name = name;
    this.age = age;
}
Person.prototype.say = function(){
    console.log('hello, my name is ' + this.name);
};
function Man(name, age) {
    // [1]
    Person.apply(this, arguments);
}
// [2]
Man.prototype = new Person();
var man1 = new Man('joe');
var man2 = new Man('david');
console.log(man1.name === man2.name);//false
console.log(man1.say === man2.say);//true
man1.say(); //hello, my name is joe

複製代碼

這時候要當心沒有覆蓋掉的方法,由於他們是公有的。

三、寄生組合繼承【最流行、最經典的繼承方式】

function Person(name, age) {
        this.name = name;
        this.age = age;
      }
      Person.prototype.say = function() {
        console.log("hello, my name is " + this.name);
      };


      function Man(name, age) {
        // 屬性繼承 
        Person.apply(this, arguments);
      }

      Man.prototype = Object.create(Person.prototype);  //a. 
      Man.prototype.constructor = Man;                  //b. 有助於instanceof的判別


      var man1 = new Man("pursue");
      var man2 = new Man("joe");

      console.log(man1.say == man2.say); // true
      console.log(man1.name == man2.name); // false
複製代碼

a對子類的原型對象和父類的原型對象作了很好的鏈接。並不像原來直接對父類原型的暴力繼承,(如Man.prototype = new Person();),這樣只是對屬性進行了暴力的覆蓋,還致使共享。

相關文章
相關標籤/搜索