[Javascript] js的類和對象

類與原型

經過原型這種機制,JavaScript 中的對象從其餘對象繼承功能特性;這種繼承機制與經典的面向對象編程語言的繼承機制不一樣.javascript

JavaScript 常被描述爲一種基於原型的語言 (prototype-based language)——每一個對象擁有一個原型對象,對象以其原型爲模板、從原型繼承方法和屬性.原型對象也可能擁有原型,並從中繼承方法和屬性,一層一層、以此類推.這種關係常被稱爲原型鏈 (prototype chain),它解釋了爲什麼一個對象會擁有定義在其餘對象中的屬性和方法.html

準確地說,這些屬性和方法定義在 Object 的構造器函數(constructor functions)之上的 prototype 屬性上,而非對象實例自己.java

在傳統的 OOP 中,首先定義「類」,此後建立對象實例時,類中定義的全部屬性和方法都被複制到實例中.在 JavaScript 中並不如此複製——而是在對象實例和它的構造器之間創建一個連接(它是proto屬性,是從構造函數的 prototype 屬性派生的),以後經過上溯原型鏈,在構造器中找到這些屬性和方法.git

理解對象的原型(能夠經過 Object.getPrototypeOf(obj)或者已被棄用的proto屬性得到)與構造函數的 prototype 屬性之間的區別是很重要的.前者是每一個實例上都有的屬性,後者是構造函數的屬性.也就是說,Object.getPrototypeOf(new Foobar())和 Foobar.prototype 指向着同一個對象.github

函數能夠有屬性, 每一個函數都有一個特殊的屬性叫做原型(prototype).編程

測試一下數組

function pro(){}
pro.inner = "pro-inner"
pro.prototype.out = "out function"
console.log(pro)

var pro2 = function(){}
pro2.inner = "pro2-inner"
pro2.prototype.out = "var varible"
console.log(pro2.prototype)

構造函數廣泛使用首字母大寫的命名方式,這不是 js 這個語言強制規定的,而是人們在使用的過程當中一種約定成俗瀏覽器

控制檯打印出來的對象安全

out: "var varible"
constructor: ƒ ()
	inner: "pro2-inner"
	length: 0
	name: "pro2"
	arguments: null
	caller: null
	prototype: {out: "var varible", constructor: ƒ}
	__proto__: ƒ ()
	[[FunctionLocation]]: pen.js:7
	[[Scopes]]: Scopes[1]
__proto__:
	constructor: ƒ Object()
	__defineGetter__: ƒ __defineGetter__()
	__defineSetter__: ƒ __defineSetter__()
	hasOwnProperty: ƒ hasOwnProperty()
	__lookupGetter__: ƒ __lookupGetter__()
	__lookupSetter__: ƒ __lookupSetter__()
	isPrototypeOf: ƒ isPrototypeOf()
	propertyIsEnumerable: ƒ propertyIsEnumerable()
	toString: ƒ toString()
	valueOf: ƒ valueOf()
	toLocaleString: ƒ toLocaleString()
	get __proto__: ƒ __proto__()
	set __proto__: ƒ __proto__()

使用 new 運算符來在如今的這個原型基礎之上,建立一個 proIns 實例.使用 new 運算符的方法就是在正常調用函數時,在函數名的前面加上一個 new 前綴. 經過這種方法,在調用函數前加一個 new ,它就會返回一個這個函數的實例化對象.閉包

var pro2 = function(){}
pro2.inner = "pro2-inner"
pro2.prototype.out = "var varible"
var proIns = new pro2()
proIns.prop = "add prop"
console.log(proIns)
console.log(proIns.prototype)
console.log(pro2.prototype)

控制檯輸出的內容

prop: "add prop"
__proto__:
	out: "var varible"
	constructor: ƒ ()
	__proto__: Object

proIns 的 proto 屬性就是 pro2.prototype,而 pro2.prototype 的proto就是 Object,當須要訪問 proIns 的某個屬性或者方法的時候,瀏覽器就會沿着原型鏈一直向上進行查找是否有該屬性/方法.

執行路徑:

var Pro = function(){}
Pro.prototype.inner="i am pro"
var proIns = new Pro()
console.log(proIns.__proto__)
console.log(proIns.__proto__.__proto__)
console.log(proIns.__proto__.__proto__.__proto__)
  • 瀏覽器首先查找 proIns 自身是否有這個屬性
  • 若是 proIns 沒有這個屬性, 而後瀏覽器就會在 proIns 的 __proto__ 也就是中查找這個屬性**(Pro.prototype)**
  • 若是 proIns 的 __proto__ 沒有這個屬性, 瀏覽器就會去查找 proIns 的 __proto____proto__ 的屬性**(Object.prototype)**
  • 最終查找proIns.__proto__.__proto__.__proto__爲空,瀏覽器判斷原型鏈上不存在該屬性,該屬性獲取 undefined

測試案例

定義一個構造器函數 Person

function Person(name, age, work) {
  this.name = name;
  this.age = age;
  this.work = work;
  this.study = function() {
    console.log(this.name + "學習了" + this.work);
  };
}

Person.prototype.eat = function() {
  console.log(this.name + "依舊須要吃飯");
};

var programmer = new Person("稱序員", 42, "coding");

programmer.study();
programmer.eat();
console.log(programmer.toString())

此時輸出 programer 在控制檯上

Person {name: "稱序員", age: 42, work: "coding", study: ƒ}
name: "稱序員"
age: 42
work: "coding"
study: ƒ ()
__proto__:
	eat: ƒ ()
	constructor: ƒ Person(name, age, work)
	__proto__:
		constructor: ƒ Object()
		__defineGetter__: ƒ __defineGetter__()
		__defineSetter__: ƒ __defineSetter__()
		hasOwnProperty: ƒ hasOwnProperty()
		__lookupGetter__: ƒ __lookupGetter__()
		__lookupSetter__: ƒ __lookupSetter__()
		isPrototypeOf: ƒ isPrototypeOf()
		propertyIsEnumerable: ƒ propertyIsEnumerable()
		toString: ƒ toString()
		valueOf: ƒ valueOf()
		toLocaleString: ƒ toLocaleString()
		get __proto__: ƒ __proto__()
		set __proto__: ƒ __proto__()

該案例總共調用了三個方法.過程以下:

  • 首先執行 study,programmer 具備該方法,就直接調用執行,輸出內容
  • 而後向下執行,執行 eat 方法,瀏覽器查找 programmer 上是否擁有該方法,瀏覽器沒有在 programmer 上查找到 eat 方法
  • 瀏覽器檢查 programmer 的原型對象(即 Person 構造函數的 prototype 屬性所指向的對象)是否具備可用的 eat() 方法,瀏覽器找到並執行該方法
  • 繼續向下執行,執行 toString 方法
  • 瀏覽器會先查找 programmer 而後查找 Person 構造器的 prototype,都沒有查找到該方法
  • 因爲原型也是對象,構造屬性 constructor 指向 Object,因此此時瀏覽器會查找 Person() 構造函數的 prototype 屬性所指向的對象的原型對象(即 Object 構造函數的 prototype 屬性所指向的對象)是否具備可用的 toString() 方法.瀏覽器查找到 toString 方法,執行.

原型鏈中的方法和屬性沒有被複制到其餘對象——它們被訪問須要經過前面所說的「原型鏈」的方式.上面的執行也是先查找 programmer,以後查找programmer.__proto__,最後查找programmer.__proto__.__proto__

沒有官方的方法用於直接訪問一個對象的原型對象=>原型鏈中的「鏈接」被定義在一個內部屬性中,在 JavaScript 語言標準中用 [[prototype]] 表示(參見 ECMAScript).然而,大多數現代瀏覽器仍是提供了一個名爲 __proto__ (先後各有 2 個下劃線)的屬性,其包含了對象的原型.

prototype 屬性

programmer 繼承的屬性和方法是定義在 prototype 屬性之上的(你能夠稱之爲子命名空間 (sub namespace) )——那些以 Object.prototype. 開頭的屬性,而非僅僅以 Object. 開頭的屬性.prototype 屬性的值是一個對象,咱們但願被原型鏈下游的對象繼承的屬性和方法,都被儲存在其中.

  • 函數是一種對象,每個函數都自動擁有 prototype 屬性
  • prototype 屬性是一個對象,默認有一個不可枚舉的 constructor 屬性,指向函數自己

因而 Object.prototype.watch()、Object.prototype.valueOf() 等等成員,適用於任何繼承自 Object() 的對象類型,包括使用構造器建立的新的對象實例.

Object.is()、Object.keys(),以及其餘不在 prototype 對象內的成員,不會被「對象實例」或「繼承自 Object() 的對象類型」所繼承.這些方法/屬性僅能被 Object() 構造器自身使用.

例如

const str = 'today is sunshine';
console.log(str.indexOf(1));

str 就至關於經過 new String()擁有了一些有用的方法

咱們常用的var obj = {},經過 Object.create 表示:

var obj = {};
// 以字面量方式建立的空對象就至關於:
var obj = Object.create(Object.prototype);

定義在 prototype 上的方法,必須在實例調用以前進行聲明.

爲何須要 prototype

用構造函數建立每個實例對象,有些屬性和方法都是如出一轍的內容,每一次生成一個實例,都必須爲重複的內容.這樣會消耗更多內存,也缺少效率.使用 prototype 讓共用屬性和方法在內存中只生成一次,而後全部實例都指向那個內存地址.

Javascript 規定,每個構造函數都有一個 prototype 屬性,指向原型對象.這個對象的全部屬性和方法,都會被構造函數的實例繼承

函數和構造函數

Function 構造函數建立一個新的 Function 對象.直接調用此構造函數可用動態建立函數,但會遭遇來自 eval 的安全問題和相對較小的性能問題.然而,與 eval 不一樣的是,Function 構造函數只在全局做用域中運行.每一個 JavaScript 函數實際上都是一個 Function 對象 => (function(){}).constructor === Function

  • 每個函數都有 prototype 屬性 => func.prototype
  • func.prototype 默認有 constructor,func.prototype.constructor
  • 構造函數指向函數自己,func.prototype.constructor===func

Object.create()

......
var singer = Object.create(programmer)

......
console.log(programmer)
console.log(singer.__proto__)
console.log(singer.__proto__ === programmer)

singer.__proto__ === programmer獲得的結果爲 true,實際Object.create()方法建立一個新對象,使用現有的對象來提供新建立的對象的proto.

constructor 屬性

每一個實例對象都從原型中繼承了一個 constructor 屬性,該屬性指向了用於構造此實例對象的構造函數.

console.log(programmer.constructor)
console.log(singer.constructor)

測試都將返回 Person() 構造器,由於該構造器包含這些實例的原始定義.

構造器是一個函數,故能夠經過圓括號調用;只需在前面添加 new 關鍵字,便能將此函數做爲構造器使用.

var loser = new programmer.constructor("落魄者",108,"快餓死了")
loser.eat() // 落魄者依舊須要吃飯

原型鏈

原型鏈的經典圖:

每一個實例對象( object )都有一個私有屬性(稱之爲 __proto__ )指向它的構造函數的原型對象(prototype ).該原型對象也有一個本身的原型對象( __proto__) ,層層向上直到一個對象的原型對象爲 null.根據定義,null 沒有原型,並做爲這個原型鏈中的最後一個環節.

幾乎全部 JavaScript 中的對象都是位於原型鏈頂端的 Object 的實例.

繼承

JavaScript 對象有一個指向一個原型對象的鏈.當試圖訪問一個對象的屬性時,它不只僅在該對象上搜尋,還會搜尋該對象的原型,以及該對象的原型的原型,依次層層向上搜索,直到找到一個名字匹配的屬性或到達原型鏈的末尾.

遵循 ECMAScript 標準,someObject.[[Prototype]] 符號是用於指向 someObject 的原型.從 ECMAScript 6 開始,[[Prototype]] 能夠經過 Object.getPrototypeOf() 和 Object.setPrototypeOf() 訪問器來訪問.這個等同於 JavaScript 的非標準但許多瀏覽器實現的屬性 __proto__.

它不該該與構造函數 func 的 prototype 屬性相混淆.被構造函數建立的實例對象的 [[prototype]] 指向 func 的 prototype 屬性.Object.prototype 屬性表示 Object 的原型對象.

function Person(name, age, work) {
  this.name = name;
  this.age = age;
  this.work = work;
  this.study = function() {
    console.log(this.name + "學習了" + this.work);
  };
}

Person.prototype.name = "Person";
Person.prototype.type= "地球人";
Person.prototype.eat = function() {
  console.log(this.name + "依舊須要吃飯");
};

var programmer = new Person("稱序員", 42, "coding");
programmer.study();
programmer.eat();
console.log(programmer)
console.log(programmer.__proto__)
console.log(programmer.constructor.prototype)

根據經典圖能夠追溯 programmer 的原型

  • programmer.__proto__指向 Person.prototype
  • programmer.__proto__.__proto__指向 Object.prototype
  • programmer.__proto__.__proto__.__proto__指向 null

屬性和方法的優先級

在實例上有一個屬性,在原型上也有屬性,優先執行近的,javascript 中的兩大鏈式 => 原型鏈和做用域鏈都是'就近原則'

function Person(name, age, work) {
  this.name = name;
  this.age = age;
  this.work = work;
  this.study = function() {
    console.log(this.name + "學習了" + this.work);
  };
}

var programmer = new Person("稱序員", 42, "coding");
programmer.eat = function(){
  console.log("幹掉了Person的eat方法")
}
programmer.type="外星人"

Person.prototype.name = "Person";
Person.prototype.type= "地球人";
Person.prototype.eat = function() {
  console.log(this.name + "依舊須要吃飯");
};

programmer.eat() // 幹掉了Person的eat方法
console.log(programmer.type) // 外星人

上面特別更改了賦值的順序,依舊是執行實例上的方法和屬性,這種狀況被稱爲"屬性遮蔽 (property shadowing)",java 語言中這就是方法重寫.

使用語法結構建立的對象

var o = {a: 1};

// o 這個對象繼承了 Object.prototype 上面的全部屬性
// o 自身沒有名爲 hasOwnProperty 的屬性
// hasOwnProperty 是 Object.prototype 的屬性
// 所以 o 繼承了 Object.prototype 的 hasOwnProperty
// Object.prototype 的原型爲 null
// 原型鏈以下:
// o ---> Object.prototype ---> null

var a = ["yo", "whadup", "?"];

// 數組都繼承於 Array.prototype
// (Array.prototype 中包含 indexOf, forEach 等方法)
// 原型鏈以下:
// a ---> Array.prototype ---> Object.prototype ---> null

function f(){
  return 2;
}

// 函數都繼承於 Function.prototype
// (Function.prototype 中包含 call, bind等方法)
// 原型鏈以下:
// f ---> Function.prototype ---> Object.prototype ---> null

使用構造器建立的對象

在 JavaScript 中,構造器其實就是一個普通的函數.當使用 new 操做符 來做用這個函數時,它就能夠被稱爲構造方法(構造函數).

function Graph() {
  this.vertices = [];
  this.edges = [];
}

Graph.prototype = {
  addVertex: function(v){
    this.vertices.push(v);
  }
};

var g = new Graph();
// g 是生成的對象,他的自身屬性有 'vertices' 和 'edges'.
// 在 g 被實例化時,g.[[Prototype]] 指向了 Graph.prototype.

使用 Object.create 建立的對象

ECMAScript 5 中引入了一個新方法:Object.create().能夠調用這個方法來建立一個新對象.新對象的原型就是調用 create 方法時傳入的第一個參數:

var a = {a: 1};
// a ---> Object.prototype ---> null

var b = Object.create(a);
// b ---> a ---> Object.prototype ---> null
console.log(b.a); // 1 (繼承而來)

var c = Object.create(b);
// c ---> b ---> a ---> Object.prototype ---> null

var d = Object.create(null);
// d ---> null
console.log(d.hasOwnProperty); // undefined, 由於d沒有繼承Object.prototype

使用 class 關鍵字建立的對象

ECMAScript6 引入了一套新的關鍵字用來實現 class.使用基於類語言的開發人員會對這些結構感到熟悉,但它們是不一樣的.JavaScript 仍然基於原型.這些新的關鍵字包括 class, constructor,static,extends 和 super.

"use strict";

class Polygon {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
}

class Square extends Polygon {
  constructor(sideLength) {
    super(sideLength, sideLength);
  }
  get area() {
    return this.height * this.width;
  }
  set sideLength(newLength) {
    this.height = newLength;
    this.width = newLength;
  }
}

var square = new Square(2);

查找原型鏈的性能

在原型鏈上查找屬性比較耗時,對性能有反作用,這在性能要求苛刻的狀況下很重要.另外,試圖訪問不存在的屬性時會遍歷整個原型鏈.

遍歷對象的屬性時,原型鏈上的每一個可枚舉屬性都會被枚舉出來.要檢查對象是否具備本身定義的屬性,而不是其原型鏈上的某個屬性,則必須使用全部對象從 Object.prototype 繼承的 hasOwnProperty 方法.

hasOwnProperty 和 Object.keys() 是 JavaScript 中處理屬性而且不會遍歷原型鏈的方法.

檢查屬性是否爲 undefined 是不可以檢查其是否存在的.該屬性可能已存在,但其值剛好被設置成了 undefined.

4 個用於拓展原型鏈的方法

  • New-initialization
  • Object.create
  • Object.setPrototypeOf
  • _proto__

prototype 和 Object.getPrototypeOf

prototype 是用於類的,而 Object.getPrototypeOf() 是用於實例的(instances),二者功能一致.

對象

一切引用類型都是對象

console.log(typeof x);    // undefined
console.log(typeof 10);   // number
console.log(typeof 'abc'); // string
console.log(typeof true);  // boolean

console.log(typeof function () {});  //function

console.log(typeof [1, 'a', true]);  //object
console.log(typeof { a: 10, b: 20 });  //object
console.log(typeof null);  //object
console.log(typeof new Number(10));  //object
  • undefined、number、string、boolean 是值類型
  • 函數、對象、數組、null

判斷值類型的用 typeof,判斷引用類型的用 instanceof

對象就是一些屬性集合

var obj = {
    a:10,
    b:function (){},
    c:function (){}
}

對象裏面一切都是屬性,方法也是屬性,以鍵值對的形式表現出來

函數定義屬性

var func = function () {

}
func.a = 10;
func.b = function () {
    console.log('hello world');
}
func.c = {
    name:'123',
    year:1988
}

對象都是經過函數建立的

function Func(){
    this.name = 'lili';
    this.year = 1988;
}
var fn1 = new Func();
var obj = {a:20,b:30};
var arr = [1,2,3];

<!--等同於-->
var obj = new Object();
obj.a = 20;
obj.b = 30;
var arr = new Array();
arr[0] = 1;
arr[1] = 2;
arr[2] = 3;

對象是函數建立的,函數是一種對象

函數和對象的關係

函數就是對象的一種

var func = function (){};
console.log(func instanceof Object); // true

對象都是經過函數進行建立的

//var obj = { a: 10, b: 20 };
//var arr = [5, 'x', true];

<!--以上代碼的本質-->
var obj = new Object();
obj.a = 10;
obj.b = 20;

var arr = new Array();
arr[0] = 5;
arr[1] = 'x';
arr[2] = true;

isPrototypeOf 判斷一個對象象是否爲一個實例的原型

console.log(a.prototype.isPrototypeOf(b));
  console.log(b.prototype.isPrototypeOf(b));

propertyIsEnumerable 方法返回一個布爾值,代表指定的屬性名是不是當前對象可枚舉的自身屬性

for(var key in obj) {
    f(obj.propertyIsEnumerable(key) {
        <!--do somethings-->
    };
};
  • 判斷給定的屬性是否能夠用 for...in 語句進行枚舉同時也是對象的自有屬性.
  • for ... in 枚舉是包含原型鏈上的屬性的,propertyIsEnumerable 做用於原型方法上時,始終是返回 false 的
  • for...in 能夠枚舉對象自己的屬性和原型上的屬性,而 propertyIsEnumerable 只能判斷自己的屬性是否能夠枚舉
  • 預約義的屬性不是可列舉的,而用戶定義的屬性老是可列舉的.因此若是你只想遍歷對象自己的屬性

原型鏈

  • 訪問一個對象的屬性時,先在基本屬性中查找,若是沒有,再沿着proto這條鏈向上找,這就是原型鏈.
  • 當咱們用 obj.xxx 訪問一個對象的屬性時,JavaScript 引擎先在當前對象上查找該屬性,若是沒有找到,就到其原型對象上找,若是尚未找到,就一直上溯到 Object.prototype 對象,最後,若是尚未找到,就只能返回 undefined.

隱式原型proto

每一個對象都已一個proto,指向建立這個對象的函數的 prototype

function Phone() {

}

var mi = new Phone();

console.log(mi instanceof Object); //true
console.log(mi instanceof Array); //false
console.log(mi instanceof Function); //false
console.log(mi instanceof Phone); //true

// 實例對象的隱式原型鏈
console.log('實例對象指向Phone.prototype:',mi.__proto__);
console.log('Phone.prototype指向Object.prototype:',mi.__proto__.__proto__);
console.log('Objec.prototype指向null:',mi.__proto__.__proto__.__proto__);

// 構造函數的隱式原型鏈
console.log('Phone.__proto__指向Function.prototype',Phone.__proto__);
console.log('Function.prototype指向Object.prototype',Phone.__proto__.__proto__);
console.log('Object.prototype指向null',Phone.__proto__.__proto__.__proto__);
var obj = new Object();
// 通常建立對象的原型鏈
console.log('建立對象的__proto__',obj.__proto__);
console.log('Object.prototype指向null',obj.__proto__.__proto__);

function Object(){

}
console.log('指向Function.prototype',Object.__proto__);
console.log('Object.prototype',Object.__proto__.__proto__);
function Function(){

}
console.log(Function.__proto__);
 console.log('Object.prototype',Function.__proto__.__proto__);

上面例子能夠知道

  • Object.prototype 對象的proto指向的是 null,特例
  • xxx.prototype 是一個對象
  • Function 和 Object 是由 Function 建立的,proto指向 Fcuntion.prototype
graph BT
Object.prototype-->|__proto__| null
Function.prototype-->|__proto__| Object.prototype
Phone.prototype-->|__proto__| Object.prototype
mi-->|__proto__| Phone.prototype
Phone-->|__proto__| Function.prototype
obj-->|__proto__| Object.prototype
Function-->|__proto__| Function.prototype
Object-->|__proto__| Function.prototype
arr-->|__proto__| Array.prototype
Array.prototype-->|__proto__| Object.prototype
  • Function.prototype/Phone.prototype/obj/Array.prototype 原型對象都是由 function Object 建立的,因此指向 Object 的原型對象
  • Phone,Function 和 Object 是由 function Function 建立的,因此指向 Function 的原型對象
  • Object 是由 Fucntion 建立,而 Function 的原型對象是由 Object 建立

constructor 屬性

  • constructor 屬性的值是一個函數對象
  • 任意函數的 fuc.prototype.constructor === fuc
  • 原型的 constructor 指向構造函數,實例 constructor 一樣指向構造函數
方法 1
function Ba(str) {
    this.name = str ? str : 'baobo';
    this.sayHello = function() {
        alert('hello');
    }
}

Ba.prototype = {
    alertA:function (){
    	alert(this.name+'-A');
    },
    alertB: function() {
        alert(this.name+'');
    },
}

var instance_b = new Ba('haha');

// constructor
console.log('原型的構造函數', Ba.prototype.constructor); //function Object(){}
console.log('實例的構造函數', instance_b.constructor); //function Object(){}

// 新定義的原型對象,並不具備constructor屬性
console.log(Ba.prototype.hasOwnProperty('constructor')); //false

上面的方法至關於重寫 Ba.prototype 對象,新定義的原型對象不包含 constructor,所以構造函數指向的 function Object(){},須要顯式的給原型添加構造函數

雖然實例對象的 constructor 和構造函數原型的 constructor 都指向構造函數,可是實例對象並不具備 constructor 這個屬性,是繼承至 Ba.prototype

console.log(ba.hasOwnProperty('constructor')); //false
console.log(Ba.prototype.hasOwnProperty('constructor')); //true
graph LR
instance_b.constructor-->|實例的構造函數| Ba
Ba.prototype.constructor-->|原型對象的構造函數| Ba

修改

Ba.prototype = {
    constructor: Ba,
    alertA: function() {
        alert(this.name + '-A');
    },
    alertB: function() {
        alert(this.name + '');
    },
}
<!--這樣就可正確指向構造函數Ba了-->

方法 2,直接在預約義的原型對象上擴展

function Ba(str) {
    this.name = str ? str : 'baobo';
    this.sayHello = function() {
        alert('hello');
    }
}

Ba.prototype.alertA = function() {
    alert(this.name + '-A');
}

Ba.prototype.alertB = function() {
    alert(this.name + 'B');
}

var instance_b = new Ba('haha');

// constructor
console.log('原型的構造函數', Ba.prototype.constructor); //f Ba(){}
console.log('實例的構造函數', instance_b.constructor); //f Ba(){}
graph TB
聲明構造函數Ba-->構造函數有prototype對象
構造函數有prototype對象-->prototype對象自動有constructor屬性
prototype對象自動有constructor屬性-->建立實例對象instance_b
建立實例對象instance_b-->繼承prototype,有instance_b.constructor
繼承prototype,有instance_b.constructor-->instance_b.constructor指向Ba
instance_b.constructor指向Ba-->對象有__proto__
對象有__proto__-->instance_b指向Ba.prototype

instanceof

  • 檢測對象是否屬性某個類 A instanceof B(驗證原型對象與實例對象之間的關係 判斷具體類型)
  • Instanceof 的判斷隊則是:沿着 A 的proto這條線來找,同時沿着 B 的prototype這條線來找,若是兩條線能找到同一個引用,即同一個對象,那麼就返回 true.若是找到終點還未重合,則返回 false.
  • instanceof 表示的就是一種繼承關係,或者原型鏈的結構
  • instanceof 不光能找直接的父級,能找父級的父級的...constructor 只能找直接的父級
function a() {
    this.name = 'alisy';
}

a.prototype.alertA = function () {
    alert(this.name);
}
function b() {
    this.name = 'baobo';
}

b.prototype.alertB = function () {
    alert(this.name);
}
function c() {
    this.name = 'cmen'
}
c.prototype = a.prototype;

//b得prototype對象指向一個c的實例,那麼全部的b的實例就能繼承c

b.prototype = new c();

b.prototype.constructor = b;

var newb = new b();

var newc = new c();

newb.alertA(); //執行baobo

newc.alertA(); //執行cmen
//instanceof

console.log(b instanceof a);

console.log(b instanceof b);

繼承:父級有的,子級也有——給父級加東西,子級也有

  • 第一種方法也是最簡單的方法,使用 call 或 apply 方法,將父對象的構造函數綁定在子對象上,即在子對象構造函數中加一行
Animal.apply(this, arguments);
                   Animal.call(this, arguments);
  • 第二種方法更常見,使用 prototype 屬性.若是"貓"的 prototype 對象,指向一個 Animal 的實例,那麼全部"貓"的實例,就能繼承 Animal 了.
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;

call 方法

  • 調用 call 的對象必須是個函數
  • 語法:call([thisObj[,arg1[, arg2[, [,.argN]]]]])
  • 定義:一個對象的一個方法調用 call,以 call 方法的第一個參數替換當前對象.
  • 說明:call 方法能夠用來代替另外一個對象調用一個方法.call 方法可將一個函數的對象上下文從初始的上下文改變爲由 thisObj 指定的新對象. 若是沒有提供 thisObj 參數,那麼 Global 對象被用做 thisObj

apply 方法

  • 語法:apply([thisObj[,argArray]])
  • 定義:應用某一對象的一個方法,用另外一個對象替換當前對象.
  • 說明:若是 argArray 不是一個有效的數組或者不是 arguments 對象,那麼將致使一個 TypeError. 若是沒有提供 argArray 和 thisObj 任何一個參數,那麼 Global 對象將被用做 thisObj, 而且沒法被傳遞任何參數.

實現繼承除了用 call 和 apply 還可使用原型鏈實現

案例一
function add(a+b){
    alert(a+b);
}
function sub(a,b){
    alert(a-b);
}
add.call(sub,3,1);

//我的理解call和apply的做用就是切換函數的對象上下文

解:用括號的第一個參數來代替this的指向,將add的執行的上下文由window切換爲sub,至關於this指向由window換成sub,add.call(sub,3,1) == add(3,1),結果爲alert(4);
注意 : js中的函數是對象,函數名是對Function對象的引用
案例二
function Animal(){
    this.name = "animal";
    this.showName = function(){
        alert(this.name);
    }
}
function Cat(){
    this.name = "cat";
}
var animal = new Animal();
var cat = new Cat();

//經過call()和apply(),將本來屬於Animal對象的方法showName交給Cat對象使用了,也就是將this指向Animal動態更改成Cat
//輸出的結果是cat
animal.showName.call(cat,"","");
//animal.showName.apply(cat,[]);
案例三:實現繼承
function Animal(name) {
    this.name = name;
    this.showName = function(name, a, b) {
        console.log('this是:' + this.name + '\na是:' + a + '\nb是:' + b);
    }
}

function Cat(name) {
    Animal.call(this, name);
    this.showLog = function() {
        console.log('hello');
    }
}

Cat.prototype.showAge = function() {
    console.log('world');
}

var cat = new Cat('hello world');

cat.showName('abc', 12, 5); //能夠直接調用showName()方法

注意:Animal.call(this);是使用Animal對象代替this對象,
this指向Animal,Cat就有了Animal對象中的方法和屬性,Cat對
象就能夠直接調用Animal對象的方法和屬性
call第二個參數開始會映射到Animal相應的參數位置
案例四:多重繼承
function Animal() {
    this.showSub = function(a, b) {
        console.log(a - b);
    }
}

function Cat() {
    this.showAdd = function(a, b) {
        console.log(a + b);
    }
}

function Dog() {
    Animal.call(this);
    Cat.call(this);
}

var a = new Dog();
a.showSub(5,3);//2
a.showAdd(5,3);//8

使用兩個或者更多的call實現多重繼承
call和apply這兩個方法差很少,區別在於call的第二個參數是任意類型,而apply的第二個參數必須是數組,也能夠是arguments

實現一些常見方法

實現 func.call(this,arg1,arg2...)

Function.prototype.call2 = function(context) {
    console.log(arguments);
    // 若是傳入的爲null,則指向window
    context = context || window;
    // 函數調用的時候,this指向調用的函數
    context.fn = this;
    var args = [];
    for (var i = 1, len = arguments.length; i < len; i++) {
        args.push('arguments[' + i + ']');
    }
    // 解析字符串,執行其中的js代碼
    // 獲取返回值
    var result = eval('context.fn(' + args + ')');
    // 執行完後將添加的屬性刪除
    delete context.fn;
    return result;
};


var heo = 'hello world';

var foo = {
    name: 'lili'
}

function func(age, sex) {
    console.log(age);
    console.log(sex);
    console.log(this.name);
    return {
        name: this.name,
        age: age,
        sex: sex,
    }
}
func.call2(null);
console.log(func.call2(foo, 23, '男'))

實現 func.apply(this,[])

  1. 修改 func 函數的 this 指向
  2. 執行 func 函數
Function.prototype.newApply = function(context, arr) {
    var result, i, len;
    context = context || window;
    context.fn = this;

    if (!arr) {
        result = context.fn;
    } else {
        var args = [];
        for (i = 0, len = arr.length; i < len; i++) {
            args.push('arr[' + i + ']');
        }
        result = eval('context.fn(' + args + ')');
    }

    delete context.fn;
    return result;
}

var obj = {
    name: 'alice'
}

function func(age, sex) {
    console.log(age);
    console.log(sex);
    console.log(this.name);
    return {
        name: this.name
    }
}

console.log(func.newApply(obj, [23, '女']));

實現一個 bind => newBind

寫程序的一個錯誤,this 丟失原先的對象,將對象的方法進行賦值以後再執行,因而變成 window.new_showA(),this 指向全局對象 window,

var a = 1;
    var obj = {
        a: 11,
        showA: function () {
            console.log(this.a);
        }
    };
    obj.showA();
    var new_showA = obj.showA;

    new_showA(); //1

因此此時須要修改 this 指向

var new_showA = obj.showA.bind(this);

new_showA(); //1

實現 bind 方法

Function.prototype.newBind = function (context) {
        //this是該函數的調用者
        var self = this;
        //arguments.slice(1),從1開始截取數組
        var args = Array.prototype.slice.call(arguments, 1);
        return function () {
            // 第二個arguments是返回的函數的參數
            var bindargs = Array.prototype.slice.call(arguments);
            return self.apply(context, args.concat(bindargs));
        }
    };

    var a = 9;

    var obj = {
        a: 99,
        showA: function (name, age) {
            console.log(this.a);
            console.log(name);
            console.log(age);
        }
    };
    obj.showA('alice', 12);
    var new_showA = obj.showA.newBind(obj, 'lilith');

    new_showA(23); //至關於執行obj.showA.apply(obj);

new 運算符的簡易實現

function Animal(name, age) {
        this.name = name;
        this.age = age;
        this.voice = 'miao';
    }

    Animal.prototype.type = 'mao';

    Animal.prototype.saying = function () {
        console.log(this.voice);
    };

    var cat = _new(Animal, 'mimi', 10);

    console.log('instance =>', cat);
    console.log({
        name: cat.name,
        age: cat.age,
        type: cat.type
    });

    cat.saying();


    function _new() {
        // 將僞數組arguments從頭部刪除一個,並將其其返回,此處是爲了獲取傳入的構造函數
        var Constructor = Array.prototype.shift.call(arguments);
        // obj.__proto__指向建立obj的函數的原型,也就是function Object(){}的原型,obj是一個實例對象,沒有prototype屬性
        var obj = Object.create(Constructor.prototype);
        var result = Constructor.apply(obj, arguments);
        // 若是構造函數有返回值,作一下處理,若是返回的是對象,就返回對象,不然該是什麼就是什麼
        return typeof result === 'object' ? result : obj;
    }

new 幹了什麼

  1. 獲取構造函數
  2. 經過 Object.create 生成新對象
<!--Object.create相似於-->
function Func(){};
Func.prototype = Constructor.prototype;
return new Func();
  1. 將 this 修改成新對象 obj,執行構造函數
  2. 將新對象返回

Docs

相關文章
相關標籤/搜索