JS基礎之數據類型、對象、原型、原型鏈、繼承

數據類型:

簡單數據類型:Undefined、Null、String、Number、Boolean、Symbol
複雜數據類型:Object 
// Undefined:聲明,但未初始化
// Null:空對象指針

typeof操做符(檢測基本數據類型):javascript

typeof的返回值有哪些:
1. undefined  // 聲明和未初始化的變量,使用typeof都會返回Undefined
2. boolean
3. string
4. number
5. object  // 當是object、null、array時
6. function // 函數是對象,不是一種數據類型,由於特殊,typeof把它從對象中區分出來。typeof 正則也返回function)

typeof 的用途是檢測基本數據類型,檢測引用類型數的值時,用instanceofjava

instanceof操做符(肯定實例和原型之間關係):數組

若是變量是給定引用類型的實例instanceof操做符就會返回true
例如:app

person instanceof Object  
arr instanceof Array  
pattern instanceof RegExp

(經典問題)判斷一個對象是否是數組:函數

  1. value instanceof Array
  2. Array.isArray(value)
  3. Object.prototype.toString.call(value) // [object Array]

建立對象:

1. 工廠模式

function createPerson(name, age) {
    var o = new Object()     // 顯示地建立對象
    o.name = name
    o.age = age
    o.getName = function () {
        console.log(this.name)
    }
    return o      // 最後須要return
}

其實就是寫了一個函數,每次建立對象就是調用這個函數。this

優勢:工廠函數解決了建立多個相似對象的問題
缺點:沒有解決對象識別問題(即怎樣知道一個對象的類型 constructor)

2. 構造函數模式

構造函數能夠建立特定類型的函數,像Object、Array這樣的原生構造函數。咱們能夠建立自定義的構造函數prototype

function Person(name, age) {
    this.name = name
    this.age = age
    this.getName = function() {
        console.log(this.name)
    }
}

var person1 = new Person('zhangsan', 18)
var person2 = new Person('lisi', 20)
new 操做符作了什麼:
1.建立一個新對象
2.將構造函數的做用於賦給新對象(所以this就只想新對象)
3.執行構造函數中的代碼(爲這個新對象添加屬性)
4.返回新對象

person1和person2分別保存着Person的一個不一樣實例,這兩個對象都有一個constructor(構造函數)屬性,該屬性指向Person指針

person1.constructor == Person  // true
person2.constructor == Person  // true

對象的constructor屬性最初是用來標識對象類別的。檢測對象類型仍是用instanceof更靠譜(++肯定實例和原型之間關係++)code

person1 instance Person //true        
person1 instance Object //true

構造函數還能夠在另外一個對象的做用域中調用對象

var o = new Object()
Person.call(o, 'xiaoming', 12)  // 在o的做用於調用Person構造函數,o就擁有了Person全部的屬性和方法。
o.getName()   // 'xiaoming'

call()apply()的第一個參數是誰,就是在誰的做用於中調用構造函數。

優勢:(解決了對象識別問題)建立自定義的構造函數意味着未來能夠將它的實例標識爲一種特定的類型(Person類),這也是構造函數模式賽過工廠模式的地方
缺點:構造函數的每一個方法,都要在每一個實例上從新建立一遍。所以不一樣實例上的同名函數不相等。
person1.getName == person2.getName   // false

3. 原型模式

先理解一些概念:
咱們建立的每一個函數(例如構造函數)都有一個prototype(原型)屬性,這個屬性是一個指針,指向一個對象(函數的原型對象),這個對象包含全部實例共享的屬性和方法prototype就是經過調用構造函數而建立的那個對象實例的原型對象。使用原型對象的好處是可讓全部對象實例共享它所包含的屬性和方法。
例如:給構造函數Person的原型添加屬性和方法,那實例也會共享這些屬性和方法。

function Person() {}
Person.prototype.name = 'zhangsan'
Person.prototype.getName = function() {console.log(this.name)}
var person1 = new Person()
person1.getName()   // 'zhangsan'

每一個函數都有一個prototype屬性,指向該函數的原型對象。而原型對象又有一個constructor(構造函數)屬性,這個屬性是一個指向prototype屬性所在函數的指針。

例如:Person.prototype.constructor 指向 Person

當調用構造函數建立一個實例,該實例的內部將包含一個指針_proto_,指向構造函數的原型對象。這個鏈接存在於實例與構造函數的原型對象之間,而不是存在於實例和構造函數之間。

Person.prototype.constructor-->Person      
實例person1._proto_-->Person.prototype
Person.prototype.isPrototypeOf(person1)  //true
Object.getPrototypeOf(person1) == Person.prototype  //true

hasOwnPropertyin:

hasOwnProperty檢測屬性存在於實例,仍是原型上。只有屬性值存在於實例時,才返回true
in操做符沒法檢測屬性存在於實例仍是原型上。只要經過對象能訪問到屬性值,就返回true

function Person() {}
Person.prototype.name = 'zhangsan'
var person1 = new Person()
person1.sex = '男'

person1.hasOwnProperty('name')  //false
person1.hanOwnProperty('sex')  //true
name in person1  //true
sex in person1  // true

判斷屬性僅存在於原型:

function(obj, name) {
    return !obj.hasOwnProperty(name) && (name in obj)
}

for inObject.keys

for in // 實例和原型上全部可枚舉的屬性(返回的是全部可以經過對象訪問的,可枚舉的屬性)   
Object.keys // 僅實例上可枚舉的屬性

重寫原型(從新設定constructor):

Person.prototype = {
    constructor: Person,
    name: 'zhangsan',
    getName: function() {
        console.log(this.name)
    }
}
優勢:共享函數,不須要每次建立實例都從新建立同名函數。
缺點:屬性的共享

4. 組合使用構造函數模式和原型模式(承認度最高的模式)

建立自定義類型最經常使用的方式,使用最廣範、承認度最高
集兩種模式之長構造函數模式用於定義實例屬性,原型模式用於定義方法和共享的屬性。這樣每一個實例都有本身的一份實例屬性副本,但同時又共享着對方法的引用,最大限度地節省了內存。

5. 動態原型模式
6. 寄生構造函數模式
7. 穩妥構造函數模式

建立對象總結

在沒有類的狀況下,能夠採用如下方式建立對象。

  • 工廠模式:使用簡單的函數建立對象,爲對象添加屬性和方法,而後返回對象。這個模式後來被構造函數模式所取代。
  • 構造函數模式:能夠建立自定義引用類型,能夠像建立內置對象實例同樣使用new操做符。不過構造函數模式也有缺點,即他的每一個成員都沒法獲得複用,包括函數。因爲函數能夠不侷限於任何對象,所以沒有理由不在多個對象間共享。
  • 原型模式:使用構造函數的prototype屬性來指定那些應該共享的屬性和方法。組合使用構造函數模式和原型模式時,使用構造函數定義實例屬性,使用原型定義共享的屬性和方法

繼承:

主要依靠原型鏈來實現繼承

1. 原型鏈

原型鏈的主要思想:利用原型讓一個引用類型繼承另外一個引用類型的屬性和方法

先回顧下構造函數、原型和實例的關係:
每個構造函數都有一個原型對象(prototype),原型對象都包含一個指向構造函數的指針(constructor),而實例都包含一個指向原型對象的內部指針。
那麼,假如咱們讓原型對象等於另外一個類型的實例:

function SuperType() {
    this.type = true
}
SuperType.prototype.getSuperValue = function() {
    return this.type
}
function SubType() {
    this.subtype = false
}
// 繼承了SuperType
SubType.prototype = new SuperType()   //實例賦值給原型的方式
SubType.prototype.getSubValue() {
    return this.subtype
}
var instance = new SubType()
console.log(instance.getSuperValue())  // true

以上定義了兩個類型:SuperTypeSubType。每一個類型分別有一個屬性和方法。
SubType繼承了SuperType,而繼承是經過建立SuperType實例,並將該實例賦給SubType.prototype來實現的(以一個新類型的實例重寫原型對象)。所以,原來存在於SuperType的實例中的全部屬性和方法,如今也存在於SubType.prototype中了。
最終:instance指向SubType的原型,SubType的原型又指向SuperType的原型

當訪問實例屬性時,首先在示例中搜索該屬性,若是沒找到該屬性,則會繼續搜索實力的原型,若是還沒找到,就沿着原型鏈繼續往上找。

原型鏈的問題:

  1. 原型屬性會被全部實例所共享。此方法實現繼承,原型實際上變成了另外一個類型的實例。SuperType的屬性就變成了SubType原型上的屬性了,就會被SubType的實例instance一、instance2等所繼承。
  2. 在建立子類型實例時,不能向超類型實例傳遞參數。

所以實際中不多單獨使用原型鏈。

function SuperType() {
    this.colors = ['red', 'blue']
}
function SubType() {}

SubType.prototype = new SuperType()  // 繼承了SuperType

var instance1 = new SubType()
instance1.colors.push('black')
console.log(instance.coloes)  // 'red', 'blue', 'black'

var instance2 = new SubType()
console.log(instance2.colors)  // 'red', 'blue', 'black'

2. 借用構造函數(經典繼承)

基本思想: 在子類型構造函數內部調用超類型構造函數

函數只不過是在特定環境中執行代碼的對象,所以經過使用apply()和call()方法也能夠在(未來)新建的對象上執行構造函數。
function SuperType() {
    this.colors = ['red', 'blue']
}
function SubType() {
    // 繼承了SuperType
    SuperType.call(this)
}
var instance1 = new SubType()
instance1.push('black')
console.log(instance1.colors)  //'red', 'blue', 'black'

var instance2 = new SubType()
console.log(instance2.colors)  //'red', 'blue'

經過使用call()方法或者apply()方法,咱們其實是在(未來)新建立的SubType實例的環境下調用SuperType構造函數

傳遞參數:

function SuperType(name) {
    this.name = name
}
function SubType() {
    // 繼承了SuperType,同時還傳遞了參數
    SuperType.call(this, 'zhangsan')
    this.age = 20
}
var instance = new SubType()
console.log(instance.name)   // zhangsan
console.log(instance.age)    // 20
缺點:和構造函數模式存在同樣的問題,函數沒法複用。

3. 組合繼承(最經常使用的繼承模式)

原型鏈和借用構造函數的技術結合到一塊,發揮二者之長的繼承模式

基本思想:使用原型鏈實現對原型屬性和方法的繼承,經過借用構造函數來實現對實例屬性的繼承。這樣既經過在原型上定義方法實現了函數的複用,又可以保證每一個函數都有本身的屬性。

function SuperType(name) {
    this.name = name
    this.color = ['red', 'blue']
}
SuperType.prototype.getName = function() {
    console.log(this.name)
}

function SubType(name, age) {
    SuperType.call(this, name)  // 繼承屬性
    this.age = age
}
SubType.prototype.getAge = function() {
    console.log(this.age)
}

SubType.prototype = new SuperType()  // 繼承方法
SubType.prototype.constructor = SubType

var instance1 = new SubType('zhangsan', 18)
instance1.colors.push('black')
console.log(instance1.colors)  // 'red', 'blue', 'black'
console.log(instance1.getName)  // 'zhangsan'
console.log(instance1.getAge)   // 18

var instance2 = new SubType('lisi', 20)
console.log(instance2.colors)   //  'red', 'blue'
console.log(instance2.getName)  // 'lisi'
console.log(instance2.getAge)  // 20
4. 原型式繼承
Object.create()
5. 寄生式繼承

繼承總結

javascript主要是經過原型鏈實現繼承。原型鏈的構建是經過將一個類型的實例賦值給另外一個構造函數的原型實現的。這樣,子類型可以訪問超類型的全部屬性和方法。原型鏈的問題是對象實例共享全部繼承的屬性和方法,所以不適合單獨使用。解決這個問題的技術是借用構造函數,即在子類型構造函數的內部調用超類型構造函數。這樣就能夠作到每一個實例都有本身的屬性,同時還能保證只是用構造函數模式來定義類型。使用最多的繼承模式是組合繼承,這種模式使用原型鏈繼承共享的屬性和方法,而經過借用構造函數繼承實例屬性。

ES6:

1.建立對象

1. class關鍵字
    2. 定義屬性:constructor(xxx) { this.xxx = xxx }
    3. 定義方法,方法之間不須要「;」
class Person{                   // 使用class,而不是function
    constructor(name, age=18) { // 類的傳參
        this.name = name        // 定義此類的屬性
        this.age = age
    }
    introduce() {                // 定義方法
        return `我叫${this.name},今年${this.age}歲`
    }
    sayName() {
        console.log(this.name)
    }
    sayAge() {
        console.log(this.age)
    }
}
const me = new Person('張三', 20)
console.log(me.introduce())

2.繼承

class Coder extends Person{
    constructor(name, age, job="Html") {  // 繼承父類屬性,並新加屬性
        super(name, age) // 必須傳參;子類必須在constructor中調用super,不然報錯(由於子類沒有本身的this對象,而是繼承父類的this對象並對其加工,若是不調用super,子類得不到this對象)
        this.job = job
    }
    showJob() { // 子類的新方法
        console.log(this.job)
    }
}
// 調用
const coder1 = new Coder('李四', 22, 'js')
coder1.sayName()
coder1.showJob()
相關文章
相關標籤/搜索