此篇文章主要是提炼《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