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

前言

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

本人github: github.com/Michael-lzg前端

構造函數

每一個構造函數(constructor)都有一個原型對象(prototype), 原型對象都包含一個指向構造函數的指針, 而實例(instance)都包含一個指向原型對象的內部指針.vue

咱們先來看一個例子java

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。 即:webpack

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

prototype

每一個構造函數都有一個 prototype 屬性,指向調用該構造函數而建立的實例的原型,也就是這個例子中的 person1 和 person2 的原型。git

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,這個屬性會指向該對象的原型。es6

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

constructor

每一個原型都有一個 constructor 屬性指向關聯的構造函數github

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

實例與原型

當讀取實例的屬性時,若是找不到,就會查找與對象關聯的原型中的屬性,若是還查不到,就去找原型的原型,一直找到最頂層爲止。web

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 的運算符優先級問題。

推薦文章

相關文章
相關標籤/搜索