此篇文章主要是提煉《JavaScript高級程序設計》中第六章的一些內容。編程
一:JS中OOP相關的概念函數
開始以前先總結JS中OOP相關的一些概念:測試
構造函數:JS中的構造函數就是普通的函數,當JS中的函數使用new調用時,這個函數就是構造函數。構造函數調用與普通函數調用相比會有如下兩點不一樣:this
① 在進入構造函數時,會先建立一個對象,並將構造函數的做用域賦值給這個對象(this指向這個對象)spa
② 在退出構造函數前,會默認返回建立的對象(返回this)prototype
原型對象:每一個函數都會有一個prototype屬性,函數的prototype屬性指向的就是(構造)函數的原型對象。默認狀況下函數的原型對象都會有一個constructor屬性,指向函數自身。設計
其實,JS中全部的對象都會有constructor屬性,默認指向Object。指針
function foo(){} //全部的函數都具備prototype屬性 //全部函數的原型對象(函數的prototype屬性)默認的constructor屬性指向函數自身 foo.prototype.constructor === foo; //全部對象都具備constructor屬性,默認指向Object ({}).constructor === Object; //對象實例內部都會有一個[[prototype]]指針,指向對象在建立時所依賴的原型對象 (new foo()).__proto__ === foo.prototype ({}).__proto__ === Object.prototype; foo.prototype === (new foo).__proto__; (new foo).__proto__.constructor === foo;
實例: 使用new產生的一個具體的對象code
二: 封裝類(建立自定義類型)對象
使用構造函數+原型模式封裝類。具體作法是:
① 把實例屬性(不共享的屬性)寫在構造函數中
② 把共享屬性(包含constructor)寫在函數原型對象中
function Animal(opts) { this.cate = opts.cate; } Animal.prototype = { constructor: Animal, shout :function () { console.log('animal shouted'); } }
//爲了封裝看起來更優雅,也能夠寫成下面這樣 function Animal(opts) { this.cate = opts.cate; if(typeof this.shout !== 'function') { //這樣直接將一個對象賦值給Animal.prototype會斷開進入構造函數時建立的對象的__proto__屬性與原型對象的連接 //因此實際new出來的第一個對象的__proto__上面不會有原型對象上的方法 //解決辦法是手動在後面調用一次構造函數或像下面那樣增長方法就不會斷開構造函數時建立的對象的__proto__屬性與原型對象的連接 /** * Animal.prototype.shout = function() {} * Animal.prototype.getCate = function() {} */ Animal.prototype = { constructor: Animal, shout :function () { console.log('animal shouted'); }, getCate: function() { return this.cate; }, setCate: function(cate) { this.cate = cate; } } } } //手動new一次Animal new Animal();
這種封裝的缺點:沒有實現訪問權限控制,全部的一切都是public的。
三:繼承
使用借用構造函數+原型連接實現繼承
function Animal(opts) { this.cate = opts ? opts.cate : undefined; if(typeof this.shout !== 'function') { Animal.prototype = { constructor: Animal, shout :function () { console.log('animal shouted'); }, getCate: function() { return this.cate; }, setCate: function(cate) { this.cate = cate; } } } } //手動new一次Animal new Animal(); //定義Dog類,並讓其從Animal繼承 function Dog(opts) { //繼承Animal中的屬性 Animal.call(this, opts); //增長Dog的屬性 this.name = opts.name; //... } Dog.prototype = new Animal();
Dog.constuctor = Dog; Dog.prototype.getName = function() { return this.name; }
缺點很明顯:Dog.prototype = new Animal(); 要實例化Dog會先調用一次Animal(),同時在new Dog()時,在Dog的構造函數中又會調用一次Animal(), Animal.call(this, opts);
因爲Dog.prototype = new Animal(); 只是想拿到Animal原型對象上的方法,因此咱們能夠改爲這樣,Dog.prototype = Animal.prototype; 但改爲這樣後,Dog.prototype與
Animal.prototype實際就都指向同一個對象了,因此Animal的實例也會有getName()方法。但咱們能夠先建立一個對象,再把Animal.prototype上的屬性拷貝來過,再賦值給
Dog.prototype。
function Animal(opts) { this.cate = opts ? opts.cate : undefined; } Animal.prototype = { constructor: Animal, shout :function () { console.log('animal shouted'); }, getCate: function() { return this.cate; }, setCate: function(cate) { this.cate = cate; } } //定義Dog類,並讓其從Animal繼承 function Dog(opts) { //繼承Animal中的屬性 Animal.call(this, opts); //增長Dog的屬性 this.name = opts.name; //... } Dog.prototype = (function() { var DogProto = {}, AnimalProto = Animal.prototype, key; for(key in AnimalProto) { AnimalProto.hasOwnProperty(key) DogProto[key] = AnimalProto[key]; } return DogProto; })(); Dog.prototype.constructor = Dog; Dog.prototype.getName = function() { return this.name; }
這樣改了後,也仍是有缺點: new Dog instanceof Animal;會返回false。但若是咱們只關注對象能作什麼,而不是對象的類是什麼(鴨式辨型編程)這樣作仍是達到了咱們想要的效果。
還有若是咱們平時想使用一些帶有本身方法的原生對象,但咱們又不想去直接擴展原生對象的prototype,咱們能夠像下面這樣作:
function enhancedString(str) { str = new String(str); //增長咱們的方法 !str.statsWith && (str.startsWith = function(target) { return this.indexOf(target) === 0; }); //... return str; } //測試: var str = new enhancedString('test'); //可使用string原生的方法 console.log(str.length);//4 console.log(str.slice(1));//est //也可使用咱們在string上面擴展的方法 console.log(str.startsWith('t')); //true