JavaScript是一种基于对象(object-based)的语言,但是不是一种真正的面向对象编程(OOP)的语言。而JavaScript中的继承是通过原型链的模式实现的。
1. js中创建对象-用构造器
function Animal(name){
this.name = name;
}
var dog = new Animal("贝贝");
alert(dog.constructor == Animal); //true
在JavaScript中,new命令后面跟的不识类,而是构造函数,使用后会生成一个对象的实例。生成的实例动向会自动含有一个constructor属性,指向它们的构造函数
- new运算符的缺点:
- 无法共享属性和方法
- 每一个实例对象,都有自己的属性和方法的剧本。无法做到数据共享,也是极大的资源浪费
- prototype属性
- 构造函数中引入了prototype属性,实例对象中没有该属性,prototype属性包含一个对象需要共享的属性和方法,不需要共享的属性和方法,就放在构造函数里面。
- 对象一旦创建,将会自动引用prototype对象的属性和方法。
- prototype属性中的属性和方法,在所有实例对象中都是一致的,都是同一个内存地址,指向prototype对象,因此可以提高运行效率。
- 由于所有的实例对象共享同一个prototype对象,prototype对象就好像是实例对象的原型,实例好像继承了prototype对象。
- prototype模式的验证方法
- isPrototypeOf()
- 用来判断:某个prototype对象和某个实例之间的关系。
alert(Animal.prototype.isPrototypeOf(dog)); //true
- hasOwnProperty()
- 实例对象的方法,用来判断某一个属性到底是本地属性,还是继承而来的属性
- in运算符
- 可以用来判断,某个实例是否含有某个属性,不管是不是本地属性
- 还可以用来遍历某个对象的所有属性
for(var prop in dot){ console.log('dog[" + prot + "]=" + dog[prop]); }
- isPrototypeOf()
2. 构造函数的继承
例子:
function Animal() {
this.species = "动物";
}
function Cat(name, color) {
this.name = name
this.color = color
}
- ① 构造函数绑定
- 使用call或apply方法,将父对象的构造函数绑定在子对象上,即
function Cat(name, color) { Animal.apply(this, arguments); this.name = name; this.color = color; } var cat = new Cat("贝贝", "white"); console.log(cat.species);//动物
- ② prototype模式
- 将Cat的prototype指向一个Animal的实例
Cat.prototype = new Animal(); // 完全替换了prototype的值 Cat.prototype.constructor = Cat; // prototype有一个constructor对象,指向它的构造函数 // 如果不修改替换后的prototype中constructor的值 // 则其指向的构造函数则为Animal var cat = new Cat("贝贝", "white"); console.log(cat.species); // 动物
- 关于constructor属性的作用是什么
constructor属性不影响任何JavaScript的内部属性。constructor其实没有什么用处,只是JavaScript语言设计的历史遗留物。由于constructor属性是可以变更的,所以未必真的指向对象的构造函数,只是一个提示。不过,从编程习惯上,我们应该尽量让对象的constructor指向其构造函数,以维持这个惯例。https://www.zhihu.com/question/19951896/answer/13457869
var a,b; (function(){ function A (arg1,arg2) { this.a = 1; this.b=2; } A.prototype.log = function () { console.log(this.a); } a = new A(); b = new A(); })() a.log(); // 1 b.log(); // 1 //通过以上代码我们可以得到两个对象,a,b,他们同为类A的实例。因为A在闭包里,所以现在我们是不能直接访问A的,那如果我想给类A增加新方法怎么办? // a.constructor.prototype 在chrome,firefox中可以通过 a.__proto__ 直接访问 a.constructor.prototype.log2 = function () { console.log(this.b) } a.log2(); // 2 b.log2(); // 2 //或者我想知道a的构造函数有几个参数? a.constructor.length // 2 //作者:小鱼二 //链接:https://www.zhihu.com/question/19951896/answer/67551712 //来源:知乎 //著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
- ③ 直接继承prototype
- 对第二种额改进。由于Animal对象中,不变的属性写入了Animal.prototype。所以,我们可以让Cat()跳过Animal(),直接继承Animal.prototype.
与前一种方法相比function Animal(){} Animal.prototype.species = '动物'; Cat.prototype = Animal.prototype; Cat.prototype.constructor = Cat(); var cat = new Cat("贝贝", "white"); console.log(cat.species); // 动物
- 优点:效率比较高(无需执行和建立Animal的实例),比较省空间。
- 缺点:Cat.prototype和Animal.prototype指向了同一个对象,任何对Cat.prototype的修改都会反映到Animal.prototype
- ④ 利用空对象作为中介
F是空对象,所以几乎不占内存,此时,修改Cat.prototype则不会影响到Animal.prototypevar F = function(){}; F.prototype = Animal.prototype; Cat.prototype = new F(); Cat.prototype.constructor = Cat;
//将采用空对象的方法封装成一个函数 function extend(Child, Parent) { var F = function(){}; F.prototype = Parent.prototype; Child.prototype = new F(); Child.prototype.constructor = Child; Child.uber = Parent.prototype; // 这等于在子对象上打开一条通道,可以直接调用父对象的方法。 //这一行放在这里,只是为了实现继承的完备性,纯属备用性质。 } //使用 extend(Cat, Animal); var cat = new Cat("贝贝", "white"); console.log(cat.species); // 动物
- ⑤ 拷贝继承
换一种思路:纯粹采用“拷贝”的方式来实现继承。简单来说,如果吧父对象的所有属性和方法,拷贝进子对象,也能够实现继承。function Animal(){}; Animal.prototype.species = "动物"; function extend2(Child, Parent) { var p = Parent.prototype; var c = Child.prototype; for(var i in p) { c[i] = p[i]; } c.uber = p; } //使用 extend2(Cat, Animal); var cat = new Cat("贝贝", "white"); console.log(cat.species); // 动物
3. 非构造函数的继承
不使用构造函数实现“继承”
例子
var Chinese = { nation: "中国" };
var Doctor = { career: "医生" };
目的:如何让“Doctor”去继承“中国人”
此时Doctor和Chinese都是普通对象,而非构造函数
- ① object()方法
object的作用:就是把子对象的prototype属性,指向了父对象,从而使得子对象与父对象连在一起。function object (o) { function F(){}; F.prototype = o; return new F(); } //使用 var Doctor = object(Chinese); Doctor.career = "医生"; console.log(Doctor.nation); // 中国
- ② 浅拷贝
把父对象的属性全部拷贝给子对象
问题:当父对象中的某一个属性为数组或者另一个对象时,子对象通过上述方法获得的只是一个内存地址,而不是真正的拷贝,因此存在父对象被篡改的可能。function extendCopy(p) { var c = {}; for(var i in p){ c[i] = p[i]; } c.uber = p; return c; } //使用 var Doctor = extendCopy(Chinese); Doctor.career = '医生'; console.log(Doctor.nation); // 中国
- ③ 深拷贝
把父对象的属性全部拷贝给子对象,当父对象的某个属性为数组或对象的时候,递归调用浅拷贝function deepCopy(p, c){ var c = c || {}; for(var i in p){ if(typeof p[i] === 'object'){ c[i] = (p[i].constructor === Array) ? [] : {}; deepCopy(p[i], c[i]); } else { c[i] = p[i]; } } return c; } //使用 var Doctor = deepCopy(Chinese); //此时若父对象中有属性为数组,此时子对象修改该属性则不会影响到父对象
参考:
http://www.ruanyifeng.com/blog/2011/06/designing_ideas_of_inheritance_mechanism_in_javascript.html