大廠面經--js基礎篇(原型,原型鏈,繼承)

1、js中的原型和原型鏈

和其他的面向對象編程語言不同,最開始js並沒有引入class的概念,但是js中有在大量使用對象,爲了保證對象之間的聯繫,JavaScript引入了原型與原型鏈的概念。

1.1、什麼是原型

在js中,每一個構造函數都擁有一個prototype屬性,這個屬性指向一個對象,也就是原型對象。原型對象默認擁有一個constructor屬性,指向指向它的那個構造函數,每個對象都擁有一個隱藏的屬性[[prototype]],指向它的原型對象。原型對象就是用來存放實例中共有的那部分屬性。

1.2. 什麼是原型鏈

js中所有的對象都是由它的原型對象繼承而來。而原型對象自身也是一個對象,它也有自己的原型對象,這樣層層上溯,就形成了一個類似鏈表的結構,這就是原型鏈, 所有原型鏈的終點都是Object函數的prototype屬性.

1.3. 常見的題目

Array.__proto__ ===  Function.prototype  // true
Array.__proto__.__proto__ === Object.prototype  // true
Function.prototype.__proto__ === Object.prototype  // true


function F(){}
Function.prototype.a = function(){
	console.log('a1')
}
Function.prototype.b = function(){
	console.log('b1')
}
Object.prototype.a = function(){
	console.log('a2')
}
Object.prototype.b = function(){
	console.log('b2')
}
let f = new F()
f.a()
f.b()
F.a()
F.b()

2、js中的繼承

js主要存在6種繼承方式。

2.1 原型鏈繼承

function Parent(){
    this.parent = [1,2,3]
}
Parent.prototype.getName = function(){
    console.log(this.parent)
}
function Child(child){
    this.child = child
}

Child.prototype = new Parent()
Child.prototype.constructor = Child

優點:

  • 這種方式的優點很明顯,多個實例可以共享原型鏈上定義的屬性和方法。

缺點:

  • 每個實例對引用類型的屬性的修改也會被其他實例共享,這不是我們想看到的
  • 創建child的時候無法像構造函數傳參,child實例無法初始化父類屬性

2.1 構造函數繼承

function Parent(parent){
    this.parent = parent
}
Parent.prototype.getName = function(){
    console.log(this.parent)
}
function Child(name,child){
    Parent.call(this,name)
    this.child = child
}

優點:

  • 克服了原型鏈繼承帶來的2個缺點

缺點:

  • 子類無法繼承父類原型鏈上的方法;
  • 每次生成子類實例都會執行一次父函數

2.2 組合繼承(常用的一種方式)

function Parent(parent){
    this.parent = parent
}
Parent.prototype.getName = function(){
    console.log(this.parent)
}
function Child(name,child){
    Parent.call(this,name)
    this.child = child
}
Child.prototype = new Parent()
Child.prototype.constructor = Child

優點:解決了上面兩種方法的缺點

  • 子類的構造函數會代替原型上的那個父類構造函數
  • 每個新實例引入的構造函數屬性是私有的

缺點:

  • 父類的構造函數執行兩次Parent.call(this,name)/new Parent()
  • 子類的構造函數會代替原型上的那個父類構造函數

2.4 寄生組合繼承(常用)

function Parent(parent){
    this.parent = parent
}
Parent.prototype.getName = function(){
    console.log(this.parent)
}
function Child(name,child){
    Parent.call(this,name)
    this.child = child
}

function inheritPrototype(Parent, Child){
     //創建父類原型的一個副本,把副本賦值給子類原型,而不是new Parent(),減少一次父構造函數的調用
    Child.prototype = Object.create(Parent.prototype)
	Child.prototype.constructor = Child
}

解決了組合繼承中父類構造函數執行兩次的問題

繼承擴展的知識點

1. 是Object.create的原理
Object.create => function(obj){
      var f = function(){};
      f.prototype = obj;
      return new f();
}

先在內部創建一個空構造函數
把構造函數的原型指向傳進來的obj對象
通過new創建對象並返回

2. Object.create,new Object(), {}三個的區別(百度)

Object.create是使用指定的原型proto對象及其屬性properties去創建一個新的對象,也就是我們新建的對象的原型對象是傳入create的對象。
值得注意的是當create的參數爲null的時候創建的新對象完全是一個空對象,沒有原型,也就是沒有繼承Object.prototype上的方法。

new Object()和{}本質上沒有區別,它們創建的新對象的原型指向的是 Object.prototype。

new Object()和{}在初始化的過程上有區別,前者是用構造函數實例化對象,後者是直接創建JSON對象,後者的初始化比較方便,可以在初始化的時候同時賦值。而兩種方法創建的對象在使用上都是一樣的,所以使用的時候都建議用後者,沒有new的過程比較高效。

3. new的原理
function myNew () {
    //創建一個實例對象
    let obj = new Object()
    //傳入構造函數
    let fn = Array.prototype.shift.call(arguments)
    let args = Array.prototype.slice.call(arguments,1)
    //實現繼承
    obj.__proto__ = fn.prototype
    //調用構造器,改變this指向到實例
    let result = fn.apply(obj,args)
    return typeof result === 'object' ? result : obj
}
  • 創建一個新對象
  • 將構造函數的作用域賦給新對象
  • 調用構造器,改變this指向到實例,執行構造函數中的代碼(爲這個新對象添加屬性)
  • 返回新對象。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章