在Javascript不存在類(Class)的概念,javascript中不是基於類的,而是經過構造函數(constructor)和原型鏈(prototype chains)實現的。可是在ES6中引入了Class(類)這個概念,做爲對象的模板。經過class關鍵字,能夠定義類。基本上,ES6的class能夠看做只是一個語法糖,它的絕大部分功能,ES5均可以作到,新的class寫法只是讓原型對象的寫法更加清晰、更像面向對象編程的語法而已。javascript
構造函數就是提供了一個生成對象的模板並描述對象的基本結構的函數。一個構造函數,能夠生成多個對象,每一個對象都有相同的結構。總的來講,構造函數就是對象的模板,對象就是構造函數的實例。java
構造函數的特色有:面試
a:構造函數的函數名首字母必須大寫。編程
b:內部使用this對象,來指向將要生成的對象實例。數組
c:使用new操做符來調用構造函數,並返回對象實例。函數
function Person(){ this.name = 'keith'; } var boy = new Person(); console.log(boy.name); //'keith'
全部的對象實例均可以繼承構造函數中的屬性和方法。可是,同一個對象實例之間,沒法共享屬性。性能
function Person(name, height) { this.name = name; this.height = height; this.hobby = function () { return 'watching movies'; } } var boy = new Person('keith', 180); var girl = new Person('rascal', 153); console.log(boy.name); //'keith' console.log(girl.name); //'rascal' console.log(boy.hobby === girl.hobby); //false
上面代碼中,一個構造函數Person生成了兩個對象實例boy和girl,而且有兩個屬性和一個方法。可是,它們的hobby方法是不同的。也就是說,每次使用new來調用構造函數生成一個對象實例的時候,都會建立一個hobby方法。這既沒有必要,又浪費資源,由於全部hobby方法都是一樣的行爲,徹底能夠被兩個對象實例共享。this
因此,構造函數的缺點就是:同一個構造函數的生成的對象實例之間沒法共享屬性或方法。spa
爲了解決構造函數的對象實例之間沒法共享屬性的缺點,JS 提供了prototype屬性。prototype
原型屬性(prototype)是函數特有的屬性,這個屬性是一個指針,指向一個對象,這個對象的用途就是包含全部實例共享的屬性和方法(咱們把這個對象叫作原型對象)。經過prototype定義的屬性及方法能被全部對象實例所共享,這就是prototype的意義。
經過構造函數生成對象實例時,會將對象實例的原型指向構造函數的 prototype 屬性。每個構造函數都有一個prototype屬性,函數的這個屬性值就是對象實例的原型對象。
function Person(name, height) { this.name = name; this.height = height; } Person.prototype.hobby = function () { // 生成的對象實例會共享這樣聲明的構造函數方法 return 'watching movies'; } var boy = new Person('keith', 180); var girl = new Person('rascal', 153); console.log(boy.name); //'keith' console.log(girl.name); //'rascal' console.log(boy.hobby === girl.hobby); //true 代表生成的對象實例共享着同一個方法
上面代碼中,若是將hobby方法放在原型對象上,那麼兩個實例對象都共享着同一個方法。對於構造函數來講,prototype是做爲構造函數的屬性;對於對象實例來講,prototype是對象實例的原型對象。因此prototype便是屬性,又是對象。
只要修改原型對象上的屬性和方法,變更就會馬上體如今全部對象實例上。
Person.prototype.hobby = function(){ return 'swimming'; } console.log(boy.hobby === girl.hobby); //true console.log(boy.hobby()); //'swimming' console.log(girl.hobby()); //'swimming'
上面代碼中,當修改了原型對象的hobby方法以後,兩個對象實例都發生了變化。這是由於對象實例實際上是沒有hobby方法,都是讀取原型對象的hobby方法。也就是說,當某個對象實例沒有該屬性和方法時,就會到原型對象上去查找。若是實例對象自身有某個屬性或方法,就不會去原型對象上查找。
boy.hobby = function(){ return 'play basketball'; } console.log(boy.hobby()); //'play basketball' console.log(girl.hobby()); //'swimming'
上面代碼中,boy對象實例的hobby方法修改時,就不會在繼承原型對象上的hobby方法了。不過girl仍然會繼承原型對象的方法。
總結:
a:原型對象的做用,就是定義全部對象實例所共享的屬性和方法。
b:prototype 對於構造函數來講,它是一個屬性;對於對象實例來講,它是一個原型對象。
對象的屬性和方法,有多是定義在自身,也有多是定義在它的原型對象上。因爲原型對象自己也是對象,它也有本身的原型,因此造成了一條原型鏈(prototype chain)。好比,a的原型對象是b,b的原型對象是c,以此類推。全部一切的對象的原型頂端,都是Object.prototype,即 Object 構造函數的 prototype 屬性指向的那個對象。
固然,Object.prototype對象也有本身的原型對象,那就是沒有任何屬性和方法的 null 對象,而 null 對象沒有本身的原型。
console.log(Person.prototype.isPrototypeOf(boy)) //true 假設person是boy的原型 console.log(Object.getPrototypeOf(Object.prototype)); //null 此處代表 Object.prototype 的原型對象是 null
原型鏈(prototype chain)的特色有:
a:讀取對象的某個屬性時,JavaScript 引擎先尋找對象自己的屬性,若是找不到,就到它的原型去找,若是仍是找不到,就到原型的原型去找。若是直到最頂層的 Object.prototype 仍是找不到,則返回 undefined。
b:若是對象自身和它的原型,都定義了一個同名屬性,那麼優先讀取對象自身的屬性,這叫作「覆蓋」(overiding)。
c:一級級向上在原型鏈尋找某個屬性,對性能是有影響的。所尋找的屬性在越上層的原型對象,對性能的影響越大。若是尋找某個不存在的屬性,將會遍歷整個原型鏈。
下面利用代碼來說解:
var arr = [1,2,3]; console.log(arr.length); //3 console.log(arr.valueOf()) //[1,2,3] console.log(arr.join('|')) //1|2|3
上面代碼中,定了一個數組arr,數組裏面有三個元素。咱們並無給數組添加任何屬性和方法,但是在調用 length,join(),valueOf() 時,卻不會報錯。
length 屬性是繼承自 Array.prototype 的,屬於原型對象上的一個屬性。join 方法也是繼承自 Array.prototype 的,屬於原型對象上的一個方法。這兩個方法是全部數組所共享的。當實例對象上沒有這個 length 屬性時,就會去原型對象查找。valueOf 方法是繼承自Object.prototype 的。首先,arr數組是沒有 valueOf 方法的,因此就到原型對象 Array.prototype 查找。而後,發現 Array.prototype 對象上也沒有valueOf方法。最後,再到它的原型對象 Object.prototype 查找,Object.prototype 上面有這個方法。
來看看 Array.prototype 對象和 Object.prototype 對象分別有什麼屬性和方法。
console.log(Object.getOwnPropertyNames(Array.prototype)) //["length", "toSource", "toString", "toLocaleString", "join", "reverse", "sort", "push", "pop", "shift", "unshift", "splice", "concat", "slice", "lastIndexOf", "indexOf", "forEach", "map", "filter", "reduce", "reduceRight", "some", "every", "find", "findIndex", "copyWithin", "fill", "entries", "keys", "values", "includes", "constructor", "$set", "$remove"] console.log(Object.getOwnPropertyNames(Object.prototype)) // ["toSource", "toString", "toLocaleString", "valueOf", "watch", "unwatch", "hasOwnProperty", "isPrototypeOf", "propertyIsEnumerable", "__defineGetter__", "__defineSetter__", "__lookupGetter__", "__lookupSetter__", "__proto__", "constructor"]
對象實例的constructor屬性返回對象實例的構造函數。
function A(){}; var a = new A(); console.log(a.constructor); //A
constructor 屬性其實是原型對象的屬性,這個屬性包含了一個指針,指回原構造函數,它被全部實例對象繼承。
function A(){}; var a = new A(); console.log(A.prototype.constructor === A) //true 經過原型對象訪問constructor屬性返回的是原型對象所處的構造函數 console.log(a.constructor === A.prototype.constructor); //true
console.log(A.hasOwnProperty('prototype')); //true console.log(A.prototype.hasOwnProperty('constructor')); //true
a:判斷對象實例的構造函數
function A(){}; var a = new A(); console.log(a.constructor === A) //true console.log(a.constructor === Array) //false
b:從實例新建另外一個實例
function A() {}; var a = new A(); var b = new a.constructor(); //從a.constructor間接調用構造函數。 console.log(b instanceof A); //true
instanceof 用於判斷對象是否爲某個構造函數的實例。
function A() {}; var a = new A(); console.log(a instanceof A); //true console.log(a instanceof Object); //true 對整個原型鏈上的對象都有效
注意,instanceof對象只能用於複雜數據類型(數組,對象等),不能用於簡單數據類型(布爾值,數字,字符串等)。並且 null 和 undefined 都不是對象,因此instanceof 老是返回false。
console.log([1] instanceof Array); //true console.log({} instanceof Object); //true console.log(true instanceof Boolean); //false console.log('aaa' instanceof String); //false console.log(null instanceof Object); //false console.log(undefined instanceof Object); //false
利用instanceof運算符,還能夠巧妙地解決,調用構造函數時,忘了加new命令的問題。
function Keith(name,height) { if (!this instanceof Keith) { return new Keith(name,height); } this.name = name; this.height = height; }
屬性 __proto__ 返回對象實例的原型對象,即構造函數的 prototype 屬性值,經過該屬性能夠訪問原型對象的全部方法。
var Person = function(name){ this.name = name; } var a = new Person('jack'); console.log(a.__proto__ === Person.prototype); //true 一樣的,Person也有原型對象,經過Person的 __proto__ 屬性也能夠訪問到它的原型對象,以此類推,能夠實現原型鏈的向上追溯。
能夠經過 __proto__ 屬性繼承其餘對象的屬性,但很是不建議這麼作,對性能影響很是大。詳情查看:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/proto
var Person = function (name) { this.name = name; } var a = new Person('jack'); console.log(a.name); //jack var b = { age: 10 } a.__proto__ = b; console.log(a.name,a.age); //jack 10
相對於經過 __proto__ 屬性繼承其餘對象的屬性而言,Object.create() 方法是一個更加值得推薦的方法。該方法接收一個對象做爲參數,返回一個以該對象爲原型對象的新對象,即繼承了做爲參數的對象的屬性及方法。
let b = { age: 10 } let a = Object.create(b); console.log(a.age); //10
Function 和 Object 其實就是一個構造函數。
function Person(a,b){ this.a = a; this.b = b; } let man = new Person('a','b'); console.log(Person.constructor); //輸出 ƒ Function() { [native code] } console.log(Person.constructor.constructor); //ƒ Function() { [native code] } console.log(Function == Person.constructor); //true 此處代表 Function 就是一個構造函數,而且普通函數的構造函數就是 Function console.log(Function.prototype == Person.__proto__); //true
let obj = {} console.log(obj.constructor); //輸出 ƒ Object() { [native code] } console.log(Object == obj.constructor); //true 此處代表 Object就是一個構造函數,而且普通對象的構造函數就是 Object console.log(Object.prototype == obj.__proto__); //true
面試題:是否全部的對象都繼承 Object
不是,JS 中不是全部對象都繼承Object,也有特例,null 和 undefined 不是繼承自 Object。