詳解js原型,構造函數以及class之間的原型關係

原型

概念

在構造函數建立的時候,系統默認的幫構造函數建立並關聯一個對象 這個對象就是原型java

做用

在原型中的全部屬性和方法,均可以被和其關聯的構造函數建立出來的全部的對象共享react

訪問原型

構造函數名.prototype 實例化的對象.__proto __c++

原型的簡單使用
  1. 利用對象的動態特性爲原型對象增長成員
  2. 直接替換原型對象(jq核心方法的實現 就是使用原型替換的思想)
function Person (name) {
      this.name = name
    }

    Person.prototype.fn = function () {
      console.log('hello world')
      console.log(this.name)
    }

    var p = new Person('小紅')

    p.fn()
    p.name = '曉麗'
    p.fn()
    console.log(Person.prototype)
    console.log(p)
複製代碼

image
image

1. prototype

含義: 是一個函數的屬性,這個屬性是一個指針,指向一個對象 做用: 構造函數調用 訪問該構造函數所關聯的原型對象編程

2. proto

含義: 是一個對象擁有的內置屬性,是js內部使用尋找原型鏈的屬性,經過該屬性能夠容許實例對象直接訪問到原型bash

3. constructor

含義:原型對象的constructor 指向其構造函數,若是替換了原型對象以後,這個constructor屬性就不許確,須要手動補充一下 app

image

原型鏈

image

構造函數以及js原生Object對象之間的原型關係

image

原型的注意事項

  • 當對象在訪問屬性和方法的時候,會如今自身查找,若是沒有才回去原型中找。(一級一級傳遞 造成了原型鏈)
  • 替換原型對象的時候,替換以前構造函數建立的對象A和替換以後建立的對象B,A和B的原型是不一致的。
  • 對象可以訪問的原型,就是在對象建立的那一刻,和構造函數關聯的那個原型

擴展以及延伸

image

構造函數

在不少編程語言中,如java,objectC,c++等,都存在類的概念,類中有私有屬性,私有方法等,經過類來實現面對對象的繼承,可是,在ES5以及之前中不像上面這幾種語言同樣,有嚴格的類的概念。js經過構造函數以及原型鏈來實現繼承。編程語言

特色
  • 首字母必須爲大寫,用來區分普通函數
  • 內部使用的this對象,來指向即將要生成的實例對象
  • 使用new 關鍵字來生成實例對象(下面爲new關鍵字的具體實現)
var obj = new Date()
    // 能夠分解爲
    var obj = {};
    obj.__proto__ = Date.prototype;
    Base.call(obj)

複製代碼
缺點
  • 全部的實例對象均可以繼承構造器函數中的屬性和方法,可是同一個對象實例之間,沒法共享屬性。
  • 若是方法在構造函數內部,每次new一個實例對象的時候,都會建立內部的這些方法,而且不一樣的實例對象之間,不能共享這些方法,形成了資源的浪費(因而有了原型這個概念)

實現方式 (簡單列舉幾種)

構造函數模式(自定義構造函數)

構造函數與普通函數的區別函數

//構造函數
function Egperson (name,age) {
    this.name = name;
    this.age = age;
    this.sayName = function () {
        alert(this.name);
     }
}
var person = new Egperson('mike','18'); //this-->person
person.sayName();  //'mike'


//普通函數
function egPerson (name,age) {
    this.name = name;
    this.age = age;
    this.sayName = function () {
        alert(this.name);
     }
}
egPerson('alice','23'); //this-->window
window.sayName();  //'alice'

複製代碼

工廠模式

function CreatePerson(name, age, gender){
    var obj = {};
    obj.name = name;
    obj.age = age;
    obj.gender = gender;
    //因爲是函數調用模式,因此this打印出來是window
    console.log(this);
    return obj;
}
var p = CreatePerson("小明", 18, "male");  // 調用方式是函數的調用方式
複製代碼

寄生模式

function CreatePerson(name, age, gender){
    var obj = new Object();
    obj.name = name;
    obj.age = age;
    obj.gender = gender;
    //這裏的this指向new 建立出來的對象
    console.log(this);
    return obj;
}
var p = new CreatePerson("小明", 18, "male");  // 調用方式是函數的調用方式

複製代碼

動態原型模式

function Person(name, age, job) {
    //屬性
    this.name = name;
    this.age = age;
    this.job = job;
    
    //方法
    if(typeof this.sayName != "function") {
        //全部的公有方法都在這裏定義
        Person.prototype.sayName = function() {
            alert(this.name);
        };

        Person.prototype.sayJob = function() {
            alert(this.job);
        };


        Person.prototype.sayAge = function() {
            alert(this.age);
        };
    }
}

var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");

person1.sayName();        //Nicholas
person2.sayName();        //Greg


複製代碼

js實現繼承的方式: 混入式繼承,原型繼承以及經典繼承,ES6的Class也能夠實現繼承ui

Class 詳解

基本上,ES6 的class能夠看做只是一個語法糖,它的絕大部分功能,ES5 均可以作到,新的class寫法只是讓對象原型的寫法更加清晰、更像面向對象編程的語法而已。this

// ES5
function Point(x, y) {
  this.x = x;
  this.y = y;
}

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

var p = new Point(1, 2);
// ES6

//定義類
class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

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


複製代碼

特色

  • class中的constructor函數至關於ES5中的構造函數(聲明屬性以及靜態方法,這種類建立屬性和建立方法參照上面動態原型模式的構造函數。我的感受有不少類似之處
  • 類中定義方法的時候,前面不加function,後面不加,可被實例對象也就是子類繼承的方法 定義在類的prototype屬性中。
  • 類的內部定義的全部方法都是不可枚舉的
  • 類和模塊內部默認採用嚴格模式
  • 子類繼承父類之後,必須在constructor中調用時super方法,不然不能新建實例,由於子類沒有屬於本身的this對象,而是繼承了父類的this對象對其進行加工

類中的原型鏈關係

每個對象都有__proto__屬性,指向對應的構造函數的prototype屬性。Class 做爲構造函數的語法糖,同時有prototype屬性和__proto__屬性,所以同時存在兩條繼承鏈。

  1. 子類的__proto__屬性,表示構造函數的繼承,老是指向父類。
  2. 子類prototype屬性的__proto__屬性,表示實例方法的繼承,老是指向父類的prototype屬性。
class A {
}

class B extends A {
}

B.__proto__ === A // true
B.prototype.__proto__ === A.prototype // true
複製代碼

類的繼承內部實現

class A {
}

class B {
}

// B 的實例繼承 A 的實例
Object.setPrototypeOf(B.prototype, A.prototype);

// B 繼承 A 的靜態屬性
Object.setPrototypeOf(B, A);

const b = new B();

Object.setPrototypeOf = function (obj, proto) {
  obj.__proto__ = proto;
  return obj;
}
複製代碼

做爲一個對象,子類(B)的原型(__proto__屬性)是父類(A);

做爲一個構造函數,子類(B)的原型對象(prototype屬性)是父類的原型對象(prototype屬性)的實例。

Object.create(A.prototype);
// 等同於
B.prototype.__proto__ = A.prototype;
複製代碼

實例的 proto 屬性

子類實例的__proto__屬性的__proto__屬性,指向父類實例的__proto__屬性。也就是說,子類的原型的原型,是父類的原型。

var p1 = new Point(2, 3);
var p2 = new ColorPoint(2, 3, 'red');

p2.__proto__ === p1.__proto__ // false
p2.__proto__.__proto__ === p1.__proto__ // true

複製代碼

類中this指向問題

類的方法內部含有this,默認指向類的實例。可是當類中的實例方法提取出來使用的時候,this指向運行時所在環境。

解決方法(新版react中,在聲明綁定方法的時候 三種方式與此相同)

class Logger {
  constructor() {
    this.printName = this.printName.bind(this);
  }

  // ...
}

class Logger {
  constructor() {
    this.printName = (name = 'there') => {
      this.print(`Hello ${name}`);
    };
  }

  // ...
}

複製代碼

ES5與ES6 實現繼承的區別

  1. 在ES5中,繼承實質上是子類先建立屬於本身的this,而後再將父類的方法添加到this(也就是使用Parent.apply(this)的方式

  2. 而在ES6中,則是先建立父類的實例對象this,而後再用子類的構造函數修改this。

相關文章
相關標籤/搜索