JS基礎總結(2)——原型與原型鏈

lzg9527發表於2020-01-20

前言

農曆2019即將過去,趁著年前幾天上班事情少,整理了一下javascript的基礎知識,在此給大家做下分享,喜歡的大佬們可以給個小贊。本文在github也做了收錄。

本人github: github.com/Michael-lzg

建構函式

每個建構函式(constructor)都有一個原型物件(prototype), 原型物件都包含一個指向建構函式的指標, 而例項(instance)都包含一個指向原型物件的內部指標.

我們先來看一個例子

function Person(name, age, job) {
  this.name = name
  this.age = age
  this.job = job
  this.sayName = function() {
    alert(this.name)
  }
}
var person1 = new Person('Zaxlct', 28, 'Engineer')
var person2 = new Person('Mick', 23, 'Doctor')
複製程式碼

上面的例子中 person1 和 person2 都是 Person 的例項。這兩個例項都有一個 constructor (建構函式)屬性,該屬性(是一個指標)指向 Person。 即:

console.log(person1.constructor == Person) //true
console.log(person2.constructor == Person) //true
複製程式碼

prototype

每個建構函式都有一個 prototype 屬性,指向呼叫該建構函式而建立的例項的原型,也就是這個例子中的 person1 和 person2 的原型。

function Person() {}
Person.prototype.name = 'Zaxlct'
Person.prototype.age = 28
Person.prototype.job = 'Engineer'
Person.prototype.sayName = function() {
  alert(this.name)
}

var person1 = new Person()
person1.sayName() // 'Zaxlct'

var person2 = new Person()
person2.sayName() // 'Zaxlct'

console.log(person1.sayName == person2.sayName) //true
複製程式碼

proto

這是每一個 JavaScript 物件(除了 null )都具有的一個屬性,叫proto,這個屬性會指向該物件的原型。

function Person() {}
var person1 = new Person()
console.log(person1.__proto__ === Person.prototype) // true
複製程式碼

constructor

每個原型都有一個 constructor 屬性指向關聯的建構函式

function Person() {}
var person1 = new Person()
console.log(Person === Person.prototype.constructor) // true
console.log(person1.__proto__ === Person.prototype) // true
複製程式碼

例項與原型

當讀取例項的屬性時,如果找不到,就會查詢與物件關聯的原型中的屬性,如果還查不到,就去找原型的原型,一直找到最頂層為止。

function Person() {}

Person.prototype.name = 'Kevin'

var person = new Person()

person.name = 'Daisy'
console.log(person.name) // Daisy

delete person.name
console.log(person.name) // Kevin
複製程式碼

在這個例子中,我們給例項物件 person 新增了 name 屬性,當我們列印 person.name 的時候,結果自然為 Daisy。

但是當我們刪除了 person 的 name 屬性時,讀取 person.name,從 person 物件中找不到 name 屬性就會從 person 的原型也就是 person.proto ,也就是 Person.prototype 中查詢,幸運的是我們找到了 name 屬性,結果為 Kevin。

Object.create()

語法:Object.create(proto, [propertiesObject])

方法建立一個新物件,使用現有的物件來提供新建立的物件的 proto。

  • new Object() 通過建構函式來建立物件, 新增的屬性是在自身例項下。
  • Object.create() es6 建立物件的另一種方式,可以理解為繼承一個物件, 新增的屬性是在原型下。
// new Object() 方式建立
var a = { rep: 'apple' }
var b = new Object(a)
console.log(b) // {rep: "apple"}
console.log(b.__proto__) // {}
console.log(b.rep) // {rep: "apple"}

// Object.create() 方式建立
var a = { rep: 'apple' }
var b = Object.create(a)
console.log(b) // {}
console.log(b.__proto__) // {rep: "apple"}
console.log(b.rep) // {rep: "apple"}
複製程式碼

經典面試題

var obj1 = { name: 'one' }
obj2 = Object.create(obj1)
obj2.name = 'two'
console.log(obj1.name)
//one

var obj1 = { prop: { name: 'one' } }
obj2 = Object.create(obj1)
obj2.prop.name = 'two'
console.log(obj1.prop.name)
//two

var obj1 = { list: ['one', 'one', 'one'] }
obj2 = Object.create(obj1)
obj2.list[0] = 'two'
console.log(obj1.list[0])
//two
複製程式碼
  • 第二題先計算 obj2.prop 的值,在原型鏈中被發現,然後再計算 obj2.prop 對應的物件(不檢查原型鏈)中是否存在 name 屬性。
  • 第三題是先計算 obj2.list 屬性的值,然後賦值給 obj2.list 屬性下標為 0(屬性名為“0”)的屬性。

私有變數、函式

在函式內部定義的變數和函式如果不對外提供介面,那麼外部將無法訪問到,也就是變為私有變數和私有函式。

function Obj() {
  var a = 0 //私有變數
  var fn = function() {
    //私有函式
  }
}

var o = new Obj()
console.log(o.a) //undefined
console.log(o.fn) //undefined
複製程式碼

靜態變數、函式

當定義一個函式後通過 “.”為其新增的屬性和函式,通過物件本身仍然可以訪問得到,但是其例項卻訪問不到,這樣的變數和函式分別被稱為靜態變數和靜態函式。

function Obj() {}
  Obj.a = 0 //靜態變數
  Obj.fn = function() {
    //靜態函式
}

console.log(Obj.a) //0
console.log(typeof Obj.fn) //function

var o = new Obj()
console.log(o.a) //undefined
console.log(typeof o.fn) //undefined
複製程式碼

例項變數、函式

在物件導向程式設計中除了一些庫函式我們還是希望在物件定義的時候同時定義一些屬性和方法,例項化後可以訪問,JavaScript也能做到這樣。

function Obj(){
    this.a=[]; //例項變數
    this.fn=function(){ //例項方法    
    }
}
 
console.log(typeof Obj.a); //undefined
console.log(typeof Obj.fn); //undefined
 
var o=new Obj();
console.log(typeof o.a); //object
console.log(typeof o.fn); //function
複製程式碼

一道綜合面試題

題目如下

function Foo() {
  getName = function() {
    alert(1)
  }
  return this
}
Foo.getName = function() {
  alert(2)
}
Foo.prototype.getName = function() {
  alert(3)
}
var getName = function() {
  alert(4)
}
function getName() {
  alert(5)
}

//請寫出以下輸出結果:
Foo.getName()
getName()
Foo().getName()
getName()
new Foo.getName()
new Foo().getName()
new new Foo().getName()
複製程式碼

解讀:首先定義了一個叫 Foo 的函式,之後為 Foo 建立了一個叫 getName 的靜態屬性儲存了一個匿名函式,之後為 Foo 的原型物件新建立了一個叫 getName 的匿名函式。之後又通過函式變數表示式建立了一個 getName 的函式,最後再宣告一個叫 getName 函式。

先來劇透一下答案,再來看看具體分析

//答案:
Foo.getName() //2
getName() //4
Foo().getName() //1
getName() //1
new Foo.getName() //2
new Foo().getName() //3
new new Foo().getName() //3
複製程式碼
  1. 第一問:Foo.getName 自然是訪問 Foo 函式上儲存的靜態屬性,自然是 2
  2. 第二問,直接呼叫 getName 函式。既然是直接呼叫那麼就是訪問當前上文作用域內的叫 getName 的函式,所以跟 1 2 3 都沒什麼關係。但是此處有兩個坑,一是變數宣告提升,二是函式表示式。
    關於函式變數提示,此處省略一萬字。。。。題中程式碼最終執行時的是
function Foo() {
  getName = function() {
    alert(1)
  }
  return this
}
var getName //只提升變數宣告
function getName() {
  alert(5)
} //提升函式宣告,覆蓋var的宣告

Foo.getName = function() {
  alert(2)
}
Foo.prototype.getName = function() {
  alert(3)
}
getName = function() {
  alert(4)
} //最終的賦值再次覆蓋function getName宣告

getName() //最終輸出4
複製程式碼
  1. 第三問的 Foo().getName(); 先執行了 Foo 函式,然後呼叫 Foo 函式的返回值物件的 getName 屬性函式。這裡 Foo 函式的返回值是 this,this 指向 window 物件。所以第三問相當於執行 window.getName()。 然而這裡 Foo 函式將此變數的值賦值為function(){alert(1)}
  2. 第四問直接呼叫 getName 函式,相當於 window.getName(),答案和前面一樣。
  3. 後面三問都是考察 js 的運算子優先順序問題。

推薦文章

相關文章