JavaScript不區分類和實例的概念,而是經過原型來實現面向對象編程。
Java是從高級的抽象上設計的類和實例,而JavaScript的設計理念,聽起來就比如Heros裏的Peter,能夠複製別人的能力。JavaScript就是別人的全部屬性都拷貝過來,成爲本身的一部分,並可以保留自身的能力。java
看廖老師的圖片,應該就能感受出咋回事了,xiaoming這個實例把本身的__proto__指向Student就實現了繼承,這種繼承關係是脆弱的,也是動態能夠修改的。編程
可是JavaScript是不推薦直接使用__proto__進行繼承的。提供了一個Object.creat(原型)
來建立對象。這是JavaScript的一種原型繼承的方法,以下實例。瀏覽器
var Student = { name : "robot", height:180, run:function(){ console.log(this.name + " is running"); }, grade:()=>"4"+"grade" }; function createStudent(name){ var s = Object.create(Student); // init new object s.name = name; return s; }; var xiaoming = createStudent("xiaosfsffsfsf");
當咱們用obj.xxx訪問一個對象的屬性時,JavaScript引擎先在當前對象上查找該屬性,若是沒有找到,就到其原型對象上找,若是尚未找到,就一直上溯到Object.prototype對象,最後,若是尚未找到,就只能返回undefined。函數
以上說明JavaScript引擎有個追朔系統,優先使用當前域的進行查找,不行則向上一個原型內進行查找。工具
除了使用{...}進行建立對象,還可使用new 的方法,須要先定義一個構造函數,以下所示。若是使用new那麼這個函數就會默認返回this這個對象,若是不是用new,直接調用,那麼這個函數就返回undefined,像普通的函數同樣。this
function Student(name){ this.name = name; this.hello = function(){ alert('hello'+name); } } var stu = new Student('XiaoMing');
新建立的stu的原型鏈是spa
stu ----> Student.prototype ----> Object.prototype ----> null
用new Student建立的對象stu,還從Student上繼承了constructor屬性,它指向Student自己。prototype
stu.constructor === Student.prototype.constructor; // true Student.prototype.constructor === Student; // true Object.getPrototypeOf(stu) === Student.prototype; // true stu instanceof Student; // true
這個原型鏈仍是盜用liaoxuefeng老師的圖
能夠看出實際上Student,xiaoming,xiaohong的原型都是指向Student.prototype。當前每一個對象的hello方法都是不一樣的,屬於不一樣的對象。但根據方法查找規則,若是把hello放在Student.prototype上,就能夠實現共用同一個方法,節省內存。即:設計
function Student(name) { this.name = name; } Student.prototype.hello = function () { alert('Hello, ' + this.name + '!'); };
另外,注意到構造函數裏的屬性,都沒有通過var進行初始化,而是直接使用this.xxx進行綁定。因此若是沒用new,而是直接調用構造函數,那麼將會使this指向window,而後內部的各個屬性都將添加到window上,無心中添加全局變量。而且在strict模式下,構造函數沒有使用new進行調用,也會致使報錯。code
***調用構造函數千萬不要忘記寫new。爲了區分普通函數和構造函數,按照約定,構造函數首字母應當大寫,而普通函數首字母應當小寫,這樣,一些語法檢查工具如jslint將能夠幫你檢測到漏寫的new。***
練習
function Cat(name) { this.name = name; } Cat.prototype.say = function(){ return "Hello, "+this.name+"!"; }
在此基礎上,咱們還能夠建立一個createCat()函數,在內部封裝全部的new 操做。
function Cat(props) { this.name = props.name || '波斯貓'; this.color = props.name || '黑白'; } Cat.prototype.say = function(){ return "Hello, I am " + this.color + this.name+"!"; } function createCat(props){ return new Cat(props || {}); }
這樣就不須要new 操做了,參數也很靈活,能夠不傳入,也能夠傳少許的,其餘的屬性將會由默認的值替代。並且參數不須要考慮順序,可對收到的JSON直接生成對象。
JavaScript的原型繼承實現方式就是:
定義新的構造函數,並在內部用call()調用但願「繼承」的構造函數,並綁定this;
藉助中間函數F實現原型鏈繼承,最好經過封裝的inherits函數完成;
繼續在新的構造函數的原型上定義新方法。
廖老師的一張圖簡單扼要說明這個繼承模型。
其實3是不過重要的,由於ES6已經推出了class這個關鍵字來解決繁瑣的原型鏈繼承的複雜性,就像java同樣好,但底層其實仍是原型鏈繼承實現,這一點並無變化。
class Cat extends Animal{
constructor(name){ super(name); } say(){ return 'Hello, '+this.name+'!'; }
}
say()方法,實例依然是共享的。but,這個須要較新的瀏覽器支持,通常還用不了,可是可使用Babel這個庫去兼容這個玩意,其實我不瞭解這是個啥庫。