首頁  >  文章  >  web前端  >  JS面試高頻題 原型與原型鏈

JS面試高頻題 原型與原型鏈

little bottle
little bottle原創
2019-04-29 17:52:008810瀏覽

原型與原型鍊作為前端高頻面試題之一,相信許多小夥伴都有遇過這個問題。那麼你是否清楚完整的了解它呢?

【相關推薦:前端面試題

國際慣例,讓我們先拋出問題:

  • 什麼是原型、原型鏈
  • 它們有什麼特點
  • 它們能做什麼
  • 怎麼確定它們的關係

或許你已經有答案,或許你開始有點疑惑,無論是get 新技能或是簡單的溫習一次,讓我們一起去探究一番吧

如果文章中有出現紕漏、錯誤之處,還請看到的小夥伴多多指教,先行謝過

原型

JavaScript 是基於原型的

我們創建的每個函數都有一個prototype(原型) 屬性,這個屬性是一個指針,指向一個對象,而這個對象的用途是包含可以由特定類型的所有實例共享的屬性和方法。

簡單來說,就是當我們建立一個函數的時候,系統就會自動分配一個prototype屬性,可以用來儲存可以讓所有實例共享的屬性和方法

用一張圖來表示就更清楚了:

JS面試高頻題 原型與原型鏈

圖解:

  • #每一個建構函式都擁有一個prototype 屬性,這個屬性指向一個對象,也就是原型對象
  • 原型物件預設擁有一個constructor 屬性,指向指向它的那個建構子
  • 每個物件都有一個隱藏的屬性__proto__,指向它的原型物件
function Person(){}

var p = new Person();

p.__proto__ === Person.prototype // true

Person.prototype.constructor === Person // true

那麼,原型物件都有哪些特點呢

原型特點

function Person(){}
Person.prototype.name = 'tt';
Person.prototype.age = 18;
Person.prototype.sayHi = function() {
    alert('Hi');
}
var person1 = new Person();
var person2 = new Person();
person1.name = 'oo';
person1.name // oo
person1.age // 18
perosn1.sayHi() // Hi
person2.age // 18
person2.sayHi() // Hi

從這段程式碼我們不難看出:

  • 實例可以共享原型上面的屬性和方法
  • 實例本身的屬性會屏蔽原型上面的同名屬性,實例上面沒有的屬性會去原型上面找

#既然原型也是對象,那我們可不可以重寫這個對象呢?答案是肯定的

function Person() {}
Person.prototype = {
    name: 'tt',
    age: 18,
    sayHi() {
        console.log('Hi');
    }
}

var p = new Person()

只是當我們在重寫原型鏈的時候需要注意以下的問題:

function Person(){}
var p = new Person();
Person.prototype = {
    name: 'tt',
    age: 18
}

Person.prototype.constructor === Person // false

p.name // undefined

一圖勝過千言萬語

JS面試高頻題 原型與原型鏈

  • 在已經創建了實例的情況下重寫原型,會切斷現有實例與新原型之間的聯繫
  • 重寫原型對象,會導致原型物件的constructor 屬性指向Object ,導致原型鏈關係混亂,所以我們應該在重寫原型物件的時候指定constructor( instanceof 仍然會傳回正確的值)
Person.prototype = {
    constructor: Person
}

注意:以這種方式重設constructor 屬性會導致它的Enumerable 特性被設定成true(預設為false)

既然現在我們知道了什麼是prototype(原型)以及它的特點,那麼原型鏈又是什麼呢?

原型鏈

JavaScript 中所有的物件都是由它的原型物件繼承而來。而原型對象本身也是一個對象,它也有自己的原型對象,這樣層層上溯,就形成了一個類似鍊錶的結構,這就是原型鏈

同樣的,我們使用一張圖來描述

JS面試高頻題 原型與原型鏈

  • 所有原型鏈的終點都是Object 函數的prototype 屬性
  • Objec.prototype 所指向的原型物件同樣擁有原型,不過它的原型是null ,而null 則沒有原型

清楚了原型鏈的概念,我們就能更清楚地知道屬性的查找規則,例如前面的p 實例屬性.如果自身和原型鏈上都不存在這個屬性,那麼屬性最終的值就是undefined ,如果是方法就會拋出錯誤

class類別

ES6 提供了Class(類別) 這個概念,作為物件的模板,透過class 關鍵字,可以定義類別

#為什麼會提到class

ES6class 可以看成只是一個語法糖,它的絕大部分功能,ES5 都可以做到,新的class 寫法只是讓物件原型的寫法更清晰、更像物件導向程式設計的語法而已

class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  toString() {
    return '(' + this.x + ', ' + this.y + ')';
  }
}

// 可以这么改写
function Point(x, y) {
  this.x = x;
  this.y = y;
}

Point.prototype.toString = function () {
  return '(' + this.x + ', ' + this.y + ')';
};

class 里面定义的方法,其实都是定义在构造函数的原型上面实现实例共享,属性定义在构造函数中,所以 ES6 中的类完全可以看作构造函数的另一种写法

除去 class 类中的一些行为可能与 ES5 存在一些不同,本质上都是通过原型、原型链去定义方法、实现共享。所以,还是文章开始那句话  JavaScript是基于原型的

更多 class 问题,参考这里

关系判断

instanceof

最常用的确定原型指向关系的关键字,检测的是原型,但是只能用来判断两个对象是否属于实例关系, 而不能判断一个对象实例具体属于哪种类型

function Person(){}
var p = new Person();

p instanceof Person // true
p instanceof Object // true
hasOwnProperty

通过使用 hasOwnProperty 可以确定访问的属性是来自于实例还是原型对象

function Person() {}
Person.prototype = {
    name: 'tt'
}
var p = new Person();
p.age = 15;

p.hasOwnProperty('age') // true
p.hasOwnProperty('name') // false

原型链的问题

由于原型链的存在,我们可以让很多实例去共享原型上面的方法和属性,方便了我们的很多操作。但是原型链并非是十分完美的

function Person(){}
Person.prototype.arr = [1, 2, 3, 4];

var person1 = new Person();
var person2 = new Person();

person1.arr.push(5) 
person2.arr // [1, 2, 3, 4, 5]

引用类型,变量保存的就是一个内存中的一个指针。所以,当原型上面的属性是一个引用类型的值时,我们通过其中某一个实例对原型属性的更改,结果会反映在所有实例上面,这也是原型 共享 属性造成的最大问题

另一个问题就是我们在创建子类型(比如上面的 p)时,没有办法向超类型( Person )的构造函数中传递参数

以上是JS面試高頻題 原型與原型鏈的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn