JavaScript 基礎之原型和原型鏈

原型

原型

  • 每一個 函數 都有 prototype 對象屬性,除了 Function.prototype.bind(),它指向原型。
  • 每一個 對象 都有 __proto__ 對象,它指向建立這個對象的構造函數的原型。其實這個屬性指向了 [[prototype]],可是 [[prototype]] 是內部屬性,咱們訪問不到,因此用 __proto__ 來訪問。
  • 對象能夠經過 __proto__ 來尋找部署改對象的屬性,__proto__ 將對象鏈接起來組成 原型鏈

小結:從上圖可看出,一切皆對象,對象都有 __proto__ 屬性javascript

1.經過 new 出來的對象,其 __proto__(這個對象的屬性)指向建立這個對象的構造函數的原型;而這個構造函數的原型也是對象,它的 __proto__ 指向 Object 的原型;

function Foo(){}
const f1 = new Foo(); 
f1.__proto__ => Foo.prototype;
Foo.prototype.__proto__ => Object.prototype;
Object.prototype.__proto__ => null; // 到頭了
複製代碼

2.那咱們日常用的字面量對象呢?

const obj = {};
obj.__proto__ => Object.prototype;
Object.prototype.__proto__ => null; // 到頭了

const arr = [];
arr.__proto__ => Array.prototype;
Array.prototype.__proto__ => Object.prototype;
Object.prototype.__proto__ => null; // 到頭了
複製代碼

3.既然一切皆對象,那函數的 __proto__ 是怎樣的呢?它指向構造函數 Function 的原型:

function fn(){}
fn.__proto__ => Function.prototype;
Function.prototype.__proto__ => Object.prototype;
Object.prototype.__proto__ => null; // 到頭了
複製代碼

4. 函數都有 prototype 屬性,它的 constructor 又指回函數本身

fn.prototype.constructor => fn;
複製代碼

new

  • 新生成一個對象 let obj = new Object();
  • 得到構造函數 let Con = [].shift.call(arguments);
  • 連接到原型 obj.__proto__ = Con.prototype;
  • 綁定 this let result = Con.apply(obj, arguments);
  • 返回新對象 return typeof result === 'object' ? result : obj;

在調用new的過程當中會發生以上4件事:java

function create(){
  // 建立一個空的對象
  let obj = new Object();
  // 得到構造函數
  let Con = [].shift.call(arguments);
  // 連接到原型
  obj.__proto__ = Con.prototype;
  // 綁定 this,執行構造函數
  let result = Con.apply(obj, arguments);
  // 確保 new 出來的是個對象
  return typeof result === 'object' ? result : obj;
}
複製代碼

對於實例化對象來講,都是經過 new 產生的,不管是 function Foo() 仍是 let x = { y: 1 }app

對於建立一個對象來講,推薦使用字面量的方式建立對象(不管性能仍是可讀性)。由於你使用 new Object() 的方式建立對象須要經過做用域鏈一層層找到Object,可是你使用字面量的方式就沒這個問題了。函數

function Foo(); // function 就是個語法糖,內部等同與 new Function() let x = { y: 1 };
// 這個字面量內部也是使用了new Object();
複製代碼

對於new來講,還需注意下運算符優先級post

function Foo(){
  return this;
}
Foo.getName = function(){
  console.log('name');
}
Foo.prototype.getName = function(){
  console.log('prototypeName');
}

new Foo.getName();   // name
new Foo().getName(); // prototypeName
複製代碼

new Foo()的優先級高於new Foo,因此上面最後2行代碼可劃分執行順序性能

new (Foo.getName());

(new Foo()).getName();
複製代碼

instanceof

instanceof 可正確判斷對象的類型,由於內部機制是經過判斷對象的原型鏈中是否是能找到類型的 prototype。下面自我實現一個 instanceof 函數測試

function inof(left, right) {
  // 得到類型的原型
  right = right.prototype;
  // 得到對象的原型
  left = left.__proto__;
  // 判斷對象的類型是否等於類型的原型
  while (true) {
  	if (left === null) {
  		return false
    }
  	if (right === left) {
  		return true
    }
  	left = left.__proto__;
  }
}
複製代碼

測試一下ui

function fn(){}
inof(fn, Function); // true
inof(fn, Object); // true
複製代碼

this

它是一個會讓不少人混淆的概念,讓咱們用幾個場景規則來記住它。this

// 場景一
function fn(){
  console.log(this.a);
}
var a = 1;
fn(); // 1

// 場景二
var obj = {
  a: 2,
  fn: fn
};
obj.fn(); // 2

// 以上兩種場景 `this` 依賴於調用函數前的對象

// 場景三 - 優先級最高的, `this` 只會綁定在 `obj1` 上,不會被任何方式修改 `this` 指向
var obj1 = new fn();
obj1.a = 3;
console.log(obj1.a); // 3

// 場景四 - 利用 call、apply、bind 改變 this,這個優先級僅次於 new
fn.call(obj);  // 2
複製代碼

以上 4 種場景弄清楚了,不少代碼中的 this 就不是問題了。spa

function fn() {
  return () => {
    return () => {
      console.log(this);
    }
  }
}
console.log(fn()()()); // window
複製代碼

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

上一篇: Javascript 基礎之類型

相關文章
相關標籤/搜索