1. 程式人生 > 實用技巧 >(七)-前端-原型鏈

(七)-前端-原型鏈

7.原型及原型鏈

原型

- 函式都帶有一個prototype 屬性,這是屬性是指向建構函式的原型物件,這個物件包含所有例項共享的屬性和方法。

- 原型物件都有一個constructor 屬性,這個屬性指向所關聯的建構函式。

- 每個物件都有一個__proto__ 屬性[非標準的方法],這個屬性指向建構函式的原型 prototype

原型鏈

- 當訪問例項物件的某個屬性時,會先在這個物件本身的屬性上查詢,如果沒有找到,則會 通過 proto 屬性去原型上查詢,如果還沒有 找到則會在建構函式的原型的__ proto__中查 找, 這樣一層層向上查詢就會形成一個作用域鏈,稱為原型鏈

原型相關習題

![img](file:///C:/Users/admin/AppData/Local/Temp/msohtmlclip1/01/clip_image006.png)

![img](file:///C:/Users/admin/AppData/Local/Temp/msohtmlclip1/01/clip_image007.png)

Object.create****的作用:

let obj = {a:123};
let o = Object.create(obj);
//該函式返回了一個新的空物件,但是該空物件的__proto__是指向了obj這個引數
// 手寫Object.create
function create(proto) {
  function F() {}
  F.prototype = proto;
 
  return new F();
}

new****的執行過程是怎麼回事?

new操作符做了這些事:

· 它建立了一個全新的物件

· 它會被執行[[Prototype]](也就是__proto__)連結

· 它使this指向新建立的物件

· 通過new建立的每個物件將最終被[[Prototype]]連結到這個函式的prototype物件上

· 如果函式沒有返回物件型別Object(包含Functoin, Array, Date, RegExg, Error),那麼new表示式中的函式呼叫將返回該物件引用

//模擬new

function objectFactory() {

 const obj = new Object();

 const Constructor = [].shift.call(arguments);

 

 obj.__proto__ = Constructor.prototype;

 

 const ret = Constructor.apply(obj, arguments);

 

 return typeof ret === "object" ? ret : obj;

}

 

call,apply,bind****三者的區別?

**
** apply() 方法呼叫一個函式, 其具有一個指定的this值,以及作為一個數組(或類似陣列的物件)提供的引數fun.apply(thisArg, [argsArray])

apply 和 call 基本類似,他們的區別只是傳入的引數不同。

apply 和 call 的區別是 call 方法接受的是若干個引數列表,而 apply 接收的是一個包含多個引數的陣列。

bind()方法建立一個新的函式, 當被呼叫時,將其this關鍵字設定為提供的值,在呼叫新函式時,在任何提供之前提供一個給定的引數序列。

call做了什麼:

將函式設為物件的屬性

執行&刪除這個函式

指定this到函式並傳入給定引數執行函式

如果不傳入引數,預設指向為 window

//實現一個call方法:

Function.prototype.myCall = function(context) {

 //此處沒有考慮context非object情況

 context.fn = this;

 let args = [];

 for (let i = 1, len = arguments.length; i < len; i++) {

  args.push(arguments[i]);

 }

 context.fn(...args);

 let result = context.fn(...args);

 delete context.fn;

 return result;

};
/ 模擬 apply
Function.prototype.myapply = function(context, arr) {
  var context = Object(context) || window;
  context.fn = this;
 
  var result;
  if (!arr) {
    result = context.fn();
  } else {
    var args = [];
    for (var i = 0, len = arr.length; i < len; i++) {
      args.push("arr[" + i + "]");
    }
    result = eval("context.fn(" + args + ")");
  }
 
  delete context.fn;
  return result;
};
 
 
實現bind要做什麼
返回一個函式,繫結this,傳遞預置引數
bind返回的函式可以作為建構函式使用。故作為建構函式時應使得this失效,但是傳入的引數依然有效
 
// mdn的實現
if (!Function.prototype.bind) {
  Function.prototype.bind = function(oThis) {
    if (typeof this !== 'function') {
      // closest thing possible to the ECMAScript 5
      // internal IsCallable function
      throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
    }
 
    var aArgs   = Array.prototype.slice.call(arguments, 1),
        fToBind = this,
        fNOP    = function() {},
        fBound  = function() {
          // this instanceof fBound === true時,說明返回的fBound被當做new的建構函式呼叫
          return fToBind.apply(this instanceof fBound
                 ? this
                 : oThis,
                 // 獲取呼叫時(fBound)的傳參.bind 返回的函式入參往往是這麼傳遞的
                 aArgs.concat(Array.prototype.slice.call(arguments)));
        };
 
    // 維護原型關係
    if (this.prototype) {
      // Function.prototype doesn't have a prototype property
      fNOP.prototype = this.prototype; 
    }
    // 下行的程式碼使fBound.prototype是fNOP的例項,因此
    // 返回的fBound若作為new的建構函式,new生成的新物件作為this傳入fBound,新物件的__proto__就是fNOP的例項
    fBound.prototype = new fNOP();
 
    return fBound;
  };
}
 
 
// 簡單版
    Function.prototype.myBind = function(context,...arg){
        // this --->  fn
        var _this = this;
        return function(...ary){
            // _this(...arg)
            return _this.apply(context,arg.concat(ary))
        }
    }

實現類的繼承

類的繼承在幾年前是重點內容,有n種繼承方式各有優劣,es6普及後越來越不重要,那麼多種寫法有點『回字有四樣寫法』的意思,如果還想深入理解的去看紅寶書即可,我們目前只實現一種最理想的繼承方式。

function Parent(name) {
    this.parent = name
}
Parent.prototype.say = function() {
    console.log(`${this.parent}: 你打籃球的樣子像kunkun`)
}
function Child(name, parent) {
    // 將父類的建構函式繫結在子類上
    Parent.call(this, parent)
    this.child = name
}
/** 
 1. 這一步不用Child.prototype =Parent.prototype的原因是怕共享記憶體,修改父類原型物件就會影響子類
 2. 不用Child.prototype = new Parent()的原因是會呼叫2次父類的構造方法(另一次是call),會存在一份多餘的父類例項屬性
3. Object.create是建立了父類原型的副本,與父類原型完全隔離
*/
Child.prototype = Object.create(Parent.prototype);
Child.prototype.say = function() {
    console.log(`${this.parent}好,我是練習時長兩年半的${this.child}`);
}
// 注意記得把子類的構造指向子類本身
Child.prototype.constructor = Child;
var parent = new Parent('father');
parent.say() // father: 你打籃球的樣子像kunkun
var child = new Child('cxk', 'father');
child.say() // father好,我是練習時長兩年半的cxk

談談你對this指向的理解

this 的指向,始終堅持一個原理:this 永遠指向最後呼叫它的那個物件

改變 this 的指向我總結有以下幾種方法:

· 使用 ES6 的箭頭函式

· 在函式內部使用 _this = this

· 使用 apply、call、bind

· new 例項化一個物件

全域性作用域下的this指向window

如果給元素的事件行為繫結函式,那麼函式中的this指向當前被繫結的那個元素

函式中的this,要看函式執行前有沒有 . , 有 . 的話,點前面是誰,this就指向誰,如果沒有點,指向window

自執行函式中的this永遠指向window

定時器中函式的this指向window

建構函式中的this指向當前的例項

call、apply、bind可以改變函式的this指向

箭頭函式中沒有this,如果輸出this,就會輸出箭頭函式定義時所在的作用域中的this