天天看點

我了解的call、apply、bind和__proto__、prototype

第一部分

call

apply

bind

  • call、apply、bind都是修改函數内部的this指向。
  • 函數調用call方法,函數内部的this指向call方法的第一個參數,call方法的其他參數會作為函數的實參傳遞,函數會立即執行。
  • 函數調用apply方法,函數内部的this指向apply方法的第一個參數,apply方法的第二個參數是函數的實參組成的數組,函數會立即執行。
  • 函數調用bind方法,函數内部的this指向bind方法的第一個參數,然後傳回一個新的函數。bind方法第一個參數之後的所有參數和新函數的所有參數依次作為舊函數的實參傳遞。
  • 當使用new關鍵字執行個體化fn.bind傳回的函數時,fn内部this指向fn的執行個體。

下面是幾個簡單的示例:

// call.js
function fn(age) {
  console.log(this.name, age);
}
/**
* fn内部this指向全局,是以this.name傳回undefined.
* 是以fn(19)會輸出undefined 18。
* */
fn(18);
let cat = { name: 'kitty' };
/**
* 調用call方法後,fn内部的this被修改為指向cat對象,是以this.name傳回'kitty'。
* call方法第一個參數之後的所有參數依次作為實參傳給fn。
* 是以fn.call(cat, 19)會輸出'kitty' 19。
* */
fn.call(cat, 19);
           
// apply.js
function fn(age) {
  console.log(this.name, age);
}
/**
* fn内部this指向全局,是以this.name傳回undefined。
* 是以fn(18)會輸出undefined 18。
* */
fn(18);
let cat = { name: 'kitty' };
/**
* 調用apply方法後,fn内部的this被修改為指向cat對象,是以this.name傳回'kitty'。
* apply方法的第二個參數是傳給fn實參組成的數組。
* 是以fn.apply(cat, [19])會輸出'kitty' 19 
* */
fn.apply(cat, [19]);
           
// bind.js
function fn(age, emotion) {
  console.log(this.name, age, emotion);
}
/**
* fn内部this指向全局,是以this.name傳回undefined。
* 是以fn(18, 'happy')會輸出undefined 18 'happy'。
* */
fn(18, 'happy');
/**
* 調用bind方法後,fn内部的this被修改為指向cat對象,是以this.name傳回'kitty'。
* bind函數會傳回修改this指向後的新函數。
* bind第一個參數之後的所有參數和新函數的所有參數作為實參依次傳給fn。
* 是以c('angry')會輸出 'kitty' 19 'angry'。
* */
let cat = { name: 'kitty' };
let c = fn.bind(cat, 19);
c('angry');
/**
* 使用new關鍵字執行個體化fn.bind傳回的函數,fn内部的this指向fn的執行個體。
* 是以new c('sorrow')會輸出 undefined 19 'sorrow'
* */
new c('sorrow');
           

了解bind函數的特性之後,接着來寫一個bind的polyfill實作:

  • 首先聲明一下,這一段polyfill實作僅供學習交流使用。
  • 其次感謝别的部落客,寫出很多優秀的部落格供我學習。
Function.prototype._bind = function(ctx) {
  let args = Array.prototype.slice.call(arguments, 1);
  let self = this;
  let fn = function() {};
  function bind() {
    let bindArgs = Array.prototype.slice.call(arguments);
    self.apply(this instanceof self ? self : ctx, [...args, ...bindArgs]);
  }
  fn.prototype = self.prototype;
  bind.prototype = new fn();
  return bind;
};
function fn(age, emotion) {
  console.log(this.name, age, emotion);
}
/**
* 調用_bind方法後,fn内部的this被修改為指向cat對象,是以this.name傳回'kitty'。
* _bind函數會傳回修改this指向後的新函數。
* _bind第一個參數之後的所有參數和新函數的所有參數作為實參依次傳給fn。
* 是以c('happy')會輸出 'kitty' 18 'happy'
* */
let cat = { name: 'kitty' };
let c = fn._bind(cat, 18);
c('happy');
/**
* 使用new關鍵字執行個體化fn._bind傳回的函數,fn内部的this指向fn的執行個體。
* 是以new c('sorrow')會輸出 undefined 18 'sorrow'
* */
new c('sorrow');
           

第二部分

__proto__

prototype

  • 每個對象都有__proto__屬性。
  • 隻有函數才有prototype屬性。
  • 對象的__proto__屬性指向其構造函數的prototype屬性。
function Animal(name) {
  this.name = name;
}
Animal.prototype.sayName = function() {
  console.log(this.name);
};
let cat = new Animal('kitty');
/**
* cat 是 Animal 的執行個體,是以 cat 的 __proto__ 屬性指向 Animal 的 prototype屬性。
* 是以這裡輸出 true。
* */
console.log(cat.__proto__ === Animal.prototype);
           
  • 當通路對象的某個屬性時,會先查找對象本身有沒有這個屬性,如果有就傳回這個屬性的值。
  • 如果沒有,就通過對象的__proto__屬性去其構造函數的prototype對象中查找,如果有就傳回這個屬性的值。
  • 如果沒有,就通過構造函數prototype對象的__proto__繼續向上查找,直到__proto__屬性的終點null,然後傳回undefined。
  • 這種通過__proto__屬性将對象連結起來的這條鍊路就叫原型鍊。

接下來看一個小例子:

function Animal(name) {
  this.name = name;
}
Animal.prototype.sayName = function() {
  console.log(this.name);
}

function Cat(name, emotion) {
  this.name = name;
  this.emotion = emotion;
}
// 讓Cat的prototype屬性指向Animal的執行個體
Cat.prototype = new Animal();
Cat.prototype.sayEmotion = function() {
  console.log(this.emotion);
}
let cat = new Cat('kitty', 'happy');
/**
* cat.sayName
* 先查找cat對象内部是否具有sayName方法,而cat隻有name和emotion兩個屬性,并沒有sayName方法。
* 然後會通過cat對象的__proto__屬性去構造函數Cat的prototype中查找,而構造函數Cat的prototype是Animal的執行個體,Cat的prototype隻有name屬性和sayEmotion方法,也沒有sayName方法。
* 然後會通過Cat的prototype的__proto__屬性去構造函數Animal的prototype中查找(因為Cat的prototype是Animal的執行個體),這時發現Animal的prototype中定義了sayName方法,然後傳回這個方法。
* sayName方法中的this指向sayName的調用者cat,是以this.name傳回‘kitty'。
* 是以cat.sayName會輸出'kitty'。
* */
cat.sayName();
cat.sayEmotion();
           

繼續閱讀