JS:面向对象(进阶篇)
组合使用构造函数和原型模式
构造函数模式用于定义实例属性,而原型模式用于定义方法和共享属性。结果,每个实例都会有自己的一份实例属性的副本,但同时又共享这对方法的引用,最大限度的节省了内存。
function Person (name,age) { this.name = name; this.age = age; this.friends = ['cjh','csb']; } Person.prototype = { constructor:Person, sayName:function () { console.log(this.name); } } let person1 = new Person('aaa',22); let person2 = new Person('bbb',26); person1.friends.push('zxf'); // [ 'cjh', 'csb', 'zxf' ] console.log(person1.friends); // [ 'cjh', 'csb' ] console.log(person2.friends); // false console.log(person1.friends === person2.friends); //true console.log(person1.sayName === person2.sayName);
我们已经达到目的了,这种构造函数与原型混成的模式,是目前用的比较广泛的。
稳妥构造函数模式
所谓稳妥对象,指的是没有公共属性,而且其他方法也不引用this的对象。稳妥对象最适合在一些安全的环境中(这些环境会禁止使用this和new),看个例子:
function Person (name) { let o = new Object(); o.sayName=function(){ console.log(name); } return o; } let f = Person('cjh'); // cjh f.sayName(); // undefined console.log(f.name);
这种其实很少用,就是把函数的参数当作Person的属性,只有函数内部才能访问。
原型链
终于讲到最终BOSS了,原型链可谓难到一大部分刚入门JS的同学(我也是), 原型链主要作用之一就是继承,我们来看个例子:
function SuperType () { console.log('Super函数被执行') this.property = true; } SuperType.prototype.getSuperValue = function () { console.log('getSuperValue函数被执行了'); return this.property; } function SubType () { this.subProperty = false; } SubType.prototype = new SuperType(); SubType.prototype.getSubValue = function () { return this.subProperty; } let instance = new SubType(); console.log(instance.getSuperValue());
运行结果:
Super函数被执行
getSuperValue函数被执行了
true
这是实现继承的一种基本模式,定义两个类型SuperType和SubType,每个类型都有一个属性和一个方法,要实现subType继承SuperType,通过创建SuperType实例并赋值给SubType.prototype实现的。实现的本质是重写原型对象,代之以一个新类型的实例。换句话说,原来存在于SuperType的实例中的所有属性和方法,现在也存在于SubType.prototype中了。
上图来自高级JS程序设计
这里我想说的是,还有种继承的方法,还更安全点,Object.create
上面的运行结果也看出来了,SuperType里面的代码被执行了,这就是new的能力,new的作用其实就是:
//new的时候做了什么 //会执行Base里面的代码 var o1 = new Base(); o1.[[Prototype]] = Base.prototype; Base.call(o1);
而Object.create做了些什么:
/Object.create = function (o) { var F = function () {}; //没有执行o函数里面的代码 F.prototype = o; return new F(); };
所以上面的继承我们可以改为:
function SuperType () { console.log('Super函数被执行了'); this.property = true; } SuperType.prototype.getSuperValue = function () { console.log('getSuperValue函数被执行了'); return this.property; } function SubType () { this.subProperty = false; } SubType.prototype = Object.create(SuperType.prototype); SubType.prototype.getSubValue = function () { return this.subProperty; } let instance = new SubType(); console.log(instance.getSuperValue());
运行结果:
getSuperValue函数被执行了
undefined
因为没有执行SuperType里面的函数,当这个函数里面要是创建对象和返回对象就会造成内存泄漏,所有this.property没有被声明和赋值,返回undefined,但是继承了所有方法和没有在函数里面声明的属性(在外面声明的):
//这个函数没有被执行到
function SuperType () { console.log('Super函数被执行了'); this.property = true; }
//这个函数有被执行到 SuperType.prototype.getSuperValue = function () { console.log('getSuperValue函数被执行了'); return this.property; } SuperType.prototype.name = 'cjh'; function SubType () { this.subProperty = false; } SubType.prototype = Object.create(SuperType.prototype); SubType.prototype.getSubValue = function () { return this.subProperty; } let instance = new SubType();
//undefined console.log(instance.property)
//cjh console.log(instance.name);
//[Function: SuperType]
console.log(SuperType.prototype.constructor);
//[Function: SuperType]
console.log(SubType.prototype.constructor);
我们看最后两个结果:都是[Function: SuperType],实际上,我们没有改变SubType.prototype的constructor的指向,还记得在JS面向对象(基础篇)里面讲过,默认情况下,所以原型对象都会自动获得一个constructor(构造函数)属性,并且constructor包含一个指向prototype属性所在函数的指针,我们改变了SubType.prototype=SuperType.prototype,所有construtor就自动指向了SuperType。
原型链的问题
function SuperType () { this.colors = ['red','green']; } function SubType () { } SubType.prototype = new SuperType(); let instance1 = new SubType(); instance1.colors.push("black"); // [ 'red', 'green', 'black' ] console.log(instance1.colors); let instance2 = new SubType(); // [ 'red', 'green', 'black' ] console.log(instance2.colors);
在基本篇的时候讲过用原型模式来创建对象有个很大的问题:就是会共享属性,这个是我们不想看到的,因为每个对象都应该有它自己的一块内存,所有那时我们用组合模式来解决那个问题(就是在构造函数里面定一个各个对象的属性),一样的,现在也可以用组合继承来解决这个问题:
//原型链的组合继承 function SuperType (name) {
console.log('调用了Supertype'); this.name = name; this.colors = ['red','green']; } SuperType.prototype.sayName = function () { console.log(this.name); } function SubType (name) { //继承属性
//给每个实例分配自己的属性地址 SuperType.call(this, name); }
//第一次调用SuperType(); SubType.prototype = new SuperType();
//第二次调用SuperType(); 因为SubType函数里面的SuperType.call(); let instance1 = new SubType('cjh',22);
//第三次调用SuperType();因为SubType函数里面的SuperType.call(); let instance2 = new SubType('csb',24); instance1.colors.push('black'); // [ 'red', 'green', 'black' ] console.log(instance1.colors); // [ 'red', 'green' ] console.log(instance2.colors); // cjh instance1.sayName(); // csb instance2.sayName();
这样是可以解决我们的问题,但是再看下结果:
调用了Supertype
调用了Supertype
调用了Supertype
[ 'red', 'green', 'black' ]
cjh
csb
上面的SuperType被执行了三次,但我们就创建了两个对象,第一次纯属多余,要想帮法去掉,在上面讲过我们用new关键字时,JS到时做了什么:
//new的时候做了什么 //会执行Base里面的代码 var o1 = new Base(); o1.[[Prototype]] = Base.prototype; Base.call(o1);
看到了call这个函数,就是调用Base()并且把o1传进去,但是这里的call没有任何作用,因为我们还没有创建对象时,它就call了。有个终极蛇皮版:
//subType:子类 superType:父类 => subType继承于superType function inheritPrototype (subType, superType) { let prototype = Object.create(superType.prototype); //因为重写了subType的原型而失去的默认的constructor,所以指回subType prototype.constructor = subType; subType.prototype = prototype; } function SuperType (name) { console.log('调用了Supertype构造函数'); this.name = name; this.colors = ['red','blue','green']; } SuperType.prototype.sayName = function () { console.log(this.name); } function SubType (name,age) { console.log('调用了SupType构造函数'); SuperType.call(this, name); this.age = age; } inheritPrototype(SubType, SuperType); SubType.prototype.sayAge = function () { console.log(this.age); } let instance1 = new SubType('cjh',22); let instance2 = new SubType('csb',25); instance1.colors.push('black'); // [ 'red', 'blue', 'green', 'black' ] console.log(instance1.colors); // [ 'red', 'blue', 'green' ] console.log(instance2.colors); // true console.log(instance1.sayName === instance2.sayName); // true console.log(instance1.sayAge === instance2.sayAge); //false console.log(instance1.name === instance2.name); // 22 instance1.sayAge(); // 25 instance2.sayAge(); // cjh instance1.sayName(); // csb instance2.sayName(); // true console.log(instance1 instanceof SubType); //[Function: SubType],要是没写prototype.constructor = subType;, //结果就是:[Function: SubType],亲测 // [Function: SubType] console.log(instance1.constructor);
运行结果:
调用了SupType构造函数
调用了Supertype构造函数
调用了SupType构造函数
调用了Supertype构造函数
现在创建一个对象分别调用一次SuperType和SubType,看上面的结果,不管是父类的方法还是子类的方法是共享的,然后属性却都是自己的内存里面的,这很符合我们的要求,即方便又省内存,到此,就差不多了,有错的话欢迎指正👏。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步