【JavaScript 面向对象编程——《原型继承》】计数器例子

    技术2022-07-11  88

    相关文章导航

         【JavaScript 面向对象编程——《深浅拷贝》】计数器例子

         【JavaScript 面向对象编程——《设计模式》】计数器例子

    👉【JavaScript 面向对象编程——《原型继承》】计数器例子👈


    文章目录

    一、原型链继承优缺点 二、借用构造函数继承优缺点 三、组合继承优缺点 四、原型式继承(1)普通原型式继承优缺点 (2)Object.create() 原型式继承优缺点 五、寄生式继承(1)普通寄生式继承优缺点 (2)Object.create() 寄生式继承优缺点 六、寄生组合式继承【最理想继承】(1)普通寄生组合式继承优缺点 (2)Object.create() 寄生组合式继承优缺点


    一、原型链继承

    <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title></title> <script type="text/javascript"> // 定义超级引用类型 function SuperType(n) { // 定义实例属性和方法【独立性】 this.num = n; this.changeBy = function(val) { this.num += val; } } // prototype(原型属性) 定义共享的方法 SuperType.prototype.add = function(val) { this.changeBy(1); }; SuperType.prototype.red = function(val) { this.changeBy(-1); }; SuperType.prototype.value = function(val) { return this.num; }; // 定义空的子引用类型(属性和方法完全继承父类,也可新增) function SubType() {} // SubType 的 prototype(原型属性),继承 SuperType 的原型成员和实例成员 SubType.prototype = new SuperType(0); // 执行操作 function event(ele, btn1, btn2, obj) { btn1.onclick = function() { obj.add(); ele.innerHTML = obj.value(); } btn2.onclick = function() { obj.red(); ele.innerHTML = obj.value(); } ele.innerHTML = obj.value(); } window.onload = function() { // 计数器 1 var Counter1 = new SubType(); var add1 = document.getElementById('add1'); var red1 = document.getElementById('red1'); var demo1 = document.getElementById('demo1'); event(demo1, add1, red1, Counter1); // 计数器 2 var Counter2 = new SubType(); var add2 = document.getElementById('add2'); var red2 = document.getElementById('red2'); var demo2 = document.getElementById('demo2'); event(demo2, add2, red2, Counter2); } </script> </head> <body> <h3>原型链继承</h3> <hr /> <p>计数器 1</p> <input type="button" id="add1" value="自增 (+1)" /> <input type="button" id="red1" value="自减 (-1)" /> <p id="demo1"></p> <hr /> <p>计数器 2</p> <input type="button" id="add2" value="自增 (+1)" /> <input type="button" id="red2" value="自减 (-1)" /> <p id="demo2"></p> </body> </html>

    优缺点

    【原理】:子类的构造函数的原型对象,是父类的构造函数创建的实例;能通过 instanceOf 和 isPrototypeOf 的检测。 【优点】:拼接了原型链。子类的实例可以从父类继承属性/方法,子类的实例是父类的实例。 【缺点】:a)子类的构造函数无法向父类的构造函数传参。定义子类的构造函数时,继承就确定了,跟子类的构造函数的执行无关;b)子类的实例共享原型对象的属性/方法,互相干扰;c)不能实现多继承。 原型对象只能有一个;

    二、借用构造函数继承

    <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title></title> <script type="text/javascript"> // 定义超级引用类型 function SuperType(n) { // 定义实例属性和方法【独立性】 this.num = n; this.changeBy = function(val) { this.num += val; }; } // 警告:父类的原型方法不能被 call() 继承,只能将方法写在函数内(成为父类实例方法,或在子类原型新增) // 定义子引用类型 function SubType(n) { // 借用构造函数继承父类实例属性及方法,还可以给父类的构造函数传递参数 SuperType.call(this, n); // 可新增实例属性和方法【独立性】 } // 在新原型上,新增共享的方法 SubType.prototype.add = function() { this.changeBy(1); }; SubType.prototype.red = function() { this.changeBy(-1); }; SubType.prototype.value = function() { return this.num; } // 执行操作 function event(ele, btn1, btn2, obj) { btn1.onclick = function() { obj.add(); ele.innerHTML = obj.value(); } btn2.onclick = function() { obj.red(); ele.innerHTML = obj.value(); } ele.innerHTML = obj.value(); } window.onload = function() { // 计数器 1 var Counter1 = new SubType(0); var add1 = document.getElementById('add1'); var red1 = document.getElementById('red1'); var demo1 = document.getElementById('demo1'); event(demo1, add1, red1, Counter1); // 计数器 2 var Counter2 = new SubType(0); var add2 = document.getElementById('add2'); var red2 = document.getElementById('red2'); var demo2 = document.getElementById('demo2'); event(demo2, add2, red2, Counter2); } </script> </head> <body> <h3>借用构造函数继承</h3> <hr /> <p>计数器 1</p> <input type="button" id="add1" value="自增 (+1)" /> <input type="button" id="red1" value="自减 (-1)" /> <p id="demo1"></p> <hr /> <p>计数器 2</p> <input type="button" id="add2" value="自增 (+1)" /> <input type="button" id="red2" value="自减 (-1)" /> <p id="demo2"></p> </body> </html>

    优缺点

    【原理】:在子类的构造函数中,通过call ( ) 或 apply ( ) 的形式,调用父类的构造函数来实现继承。 【优点】:a)解决了原型链继承方式中,子类的对象会共享父类构造函数的原型对象的问题;b)创建子类的对象时,可以向父类构造函数中传递参数;c)可以实现多继承(call / apply多个父类的构造函数); 【缺点】:a)创建的实例并不是父类的实例,只是子类的实例;b)没有拼接原型链,不能使用 instanceof。因为子类的实例只继承了父类的实例属性/方法,没有继承父类的构造函数的原型对象中的属性 / 方法;c)每个子类的实例都持有父类的实例方法的副本,浪费内存,影响性能,而且无法实现父类的实例方法的复用;

    三、组合继承

    <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title></title> <script type="text/javascript"> // 定义超级引用类型 function SuperType(n) { // 定义实例属性和方法【独立性】 this.num = n; this.changeBy = function(val) { this.num += val; } } // prototype(原型属性) 定义共享的方法 SuperType.prototype.add = function() { this.changeBy(1); }; SuperType.prototype.red = function() { this.changeBy(-1); }; // 定义子引用类型 function SubType(n) { // 借用构造函数继承父类实例属性及方法,还可以给父类的构造函数传递参数 SuperType.call(this, n); // 可新增实例属性和方法【独立性】 } // 1.同时继承了基类的原型成员和实例成员,但这样写会使父类构造函数执行两次,毕竟 call() 已继承父类实例成员 // SubType.prototype = new SuperType(); // 2.只继承基类的原型成员,而不继承实例成员。这样是最合理的,并且父类构造函数也只执行一次 SubType.prototype = SuperType.prototype; // 让 SubType 的构造函数重新指回这个类本身,否则的话它会变成 SuperType 构造函数 SubType.prototype.constructor = SubType; // 在新原型上,新增共享的方法(先继承,再新增)【可用父类原型定义,也可在子类原型新增】 SubType.prototype.value = function() { return this.num; }; // 执行操作 function event(ele, btn1, btn2, obj) { btn1.onclick = function() { obj.add(); ele.innerHTML = obj.value(); } btn2.onclick = function() { obj.red(); ele.innerHTML = obj.value(); } ele.innerHTML = obj.value(); } window.onload = function() { // 计数器 1 var Counter1 = new SubType(0); var add1 = document.getElementById('add1'); var red1 = document.getElementById('red1'); var demo1 = document.getElementById('demo1'); event(demo1, add1, red1, Counter1); // 计数器 2 var Counter2 = new SubType(0); var add2 = document.getElementById('add2'); var red2 = document.getElementById('red2'); var demo2 = document.getElementById('demo2'); event(demo2, add2, red2, Counter2); } </script> </head> <body> <h3>组合继承</h3> <hr /> <p>计数器 1</p> <input type="button" id="add1" value="自增 (+1)" /> <input type="button" id="red1" value="自减 (-1)" /> <p id="demo1"></p> <hr /> <p>计数器 2</p> <input type="button" id="add2" value="自增 (+1)" /> <input type="button" id="red2" value="自减 (-1)" /> <p id="demo2"></p> </body> </html>

    优缺点

    【原理】:组合继承仅仅是同时使用了原型链继承和构造函数继承;具体做法是,将父类的实例作为子类的构造函数的原型对象,并在子类的构造函数中调用父类的构造函数。 【优点】:a)既可以从父类的构造函数的原型对象继承方法,也能从父类的构造函数继承属性;b)既是父类的实例,也是子类的实例;c)拼接了原型链,支持 instanceof 、isPrototypeOf 检测;d)调用父类的构造函数时可以传参数;e)从父类的构造函数的原型对象继承的方法,可以被复用,被所有子类的实例共享; 【缺点】:父类的构造函数执行了两次,从而父类的实例属性被创建了两次,在子类的原型对象、子类的实例中都存在。父类的实例属性被继承了两次,子类的实例中的属性覆盖了子类的原型对象中的属性。浪费了内存和性能。

    四、原型式继承

    (1)普通原型式继承

    <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title></title> <script type="text/javascript"> // 定义对象字面量 var makeCounter = { num : 0, changeBy : function(val) { this.num += val; }, add : function() { this.changeBy(1); }, red : function() { this.changeBy(-1); }, value : function() { return this.num; } }; // 创建 object 函数(临时中转) function object(o) { // 创建子引用类型(临时) var F = function() {}; // function F() {}; // 将 o 的实例成员赋值给 F() 的原型对象 F.prototype = o; // 返回子类的一个实例 return new F(); } // 执行操作 function event(ele, btn1, btn2, obj) { btn1.onclick = function() { obj.add(); ele.innerHTML = obj.value(); } btn2.onclick = function() { obj.red(); ele.innerHTML = obj.value(); } ele.innerHTML = obj.value(); } window.onload = function() { // 计数器 1 var Counter1 = object(makeCounter); var add1 = document.getElementById('add1'); var red1 = document.getElementById('red1'); var demo1 = document.getElementById('demo1'); event(demo1, add1, red1, Counter1); // 计数器 2 var Counter2 = object(makeCounter); var add2 = document.getElementById('add2'); var red2 = document.getElementById('red2'); var demo2 = document.getElementById('demo2'); event(demo2, add2, red2, Counter2); } </script> </head> <body> <h3>原型式继承</h3> <hr /> <p>计数器 1</p> <input type="button" id="add1" value="自增 (+1)" /> <input type="button" id="red1" value="自减 (-1)" /> <p id="demo1"></p> <hr /> <p>计数器 2</p> <input type="button" id="add2" value="自增 (+1)" /> <input type="button" id="red2" value="自减 (-1)" /> <p id="demo2"></p> </body> </html>

    优缺点

    【原理】:利用工具函数,通过原型对象直接得到父类的实例,并当作子类对实例使用。 【优点】:不涉及父类的构造函数,不调用父类的构造函数就能实现继承。 【缺点】:方法在函数中定义,无法得到复用;本质上还是原型链继承,只是通过工具函数进行了封装,仍然存在子类的实例共享原型对象的问题。

    (2)Object.create() 原型式继承

    <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title></title> <script type="text/javascript"> // 定义对象字面量 var makeCounter = { num : 0, changeBy : function(val) { this.num += val; }, add : function() { this.changeBy(1); }, red : function() { this.changeBy(-1); }, value : function() { return this.num; } }; // 执行操作 function event(ele, btn1, btn2, obj) { btn1.onclick = function() { obj.add(); ele.innerHTML = obj.value(); } btn2.onclick = function() { obj.red(); ele.innerHTML = obj.value(); } ele.innerHTML = obj.value(); } window.onload = function() { // 计数器 1 var Counter1 = Object.create(makeCounter); var add1 = document.getElementById('add1'); var red1 = document.getElementById('red1'); var demo1 = document.getElementById('demo1'); event(demo1, add1, red1, Counter1); // 计数器 2 var Counter2 = Object.create(makeCounter); var add2 = document.getElementById('add2'); var red2 = document.getElementById('red2'); var demo2 = document.getElementById('demo2'); event(demo2, add2, red2, Counter2); } </script> </head> <body> <h3>"Object.create()" 实现原型式继承</h3> <hr /> <p>计数器 1</p> <input type="button" id="add1" value="自增 (+1)" /> <input type="button" id="red1" value="自减 (-1)" /> <p id="demo1"></p> <hr /> <p>计数器 2</p> <input type="button" id="add2" value="自增 (+1)" /> <input type="button" id="red2" value="自减 (-1)" /> <p id="demo2"></p> </body> </html>

    优缺点

    【原理】:在ES5中规范了 Object.create() 方法,可以用做工具函数。 【优点】:不涉及父类的构造函数,不调用父类的构造函数就能实现继承。 【缺点】:方法在函数中定义,无法得到复用;本质上还是原型链继承,只是通过工具函数进行了封装,仍然存在子类的实例共享原型对象的问题。

    五、寄生式继承

    (1)普通寄生式继承

    <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title></title> <script type="text/javascript"> // 定义对象字面量 var makeCounter = { add : function() { this.changeBy(1); }, red : function() { this.changeBy(-1); }, value : function() { return this.num; } }; // 创建 object 函数(临时中转) function object(o) { // 创建子引用类型(临时) var F = function() {}; // function F() {}; // 将 o 的实例成员赋值给 F() 的原型对象 F.prototype = o; // 返回子类的一个实例 return new F(); } // 将原型式继承再次封装【类似工厂模式】 function createObj(o, n) { // 通过调用函数创建一个新对象 var clone = object(o); // 通过给实例对象扩展属性和方法来增强这个实例对象【子类实例独有,可设参】 clone.num = n; clone.changeBy = function(val) { clone.num += val; } // 返回新对象 return clone; } // 执行操作 function event(ele, btn1, btn2, obj) { btn1.onclick = function() { obj.add(); ele.innerHTML = obj.value(); } btn2.onclick = function() { obj.red(); ele.innerHTML = obj.value(); } ele.innerHTML = obj.value(); } window.onload = function() { // 计数器 1 var Counter1 = createObj(makeCounter, 0); var add1 = document.getElementById('add1'); var red1 = document.getElementById('red1'); var demo1 = document.getElementById('demo1'); event(demo1, add1, red1, Counter1); // 计数器 2 var Counter2 = createObj(makeCounter, 0); var add2 = document.getElementById('add2'); var red2 = document.getElementById('red2'); var demo2 = document.getElementById('demo2'); event(demo2, add2, red2, Counter2); } </script> </head> <body> <h3>寄生式继承</h3> <hr /> <p>计数器 1</p> <input type="button" id="add1" value="自增 (+1)" /> <input type="button" id="red1" value="自减 (-1)" /> <p id="demo1"></p> <hr /> <p>计数器 2</p> <input type="button" id="add2" value="自增 (+1)" /> <input type="button" id="red2" value="自减 (-1)" /> <p id="demo2"></p> </body> </html>

    优缺点

    【原理】:与原型式继承完全相同,只是对父类的实例(也当作子类的实例使用)进行了增强。 【优点】:不涉及父类的构造函数,不调用父类的构造函数就能实现继承。 【缺点】:方法在函数中定义,无法得到复用;本质上还是原型链继承,只是通过工具函数进行了封装,仍然存在子类的实例共享原型对象的问题。

    (2)Object.create() 寄生式继承

    <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title></title> <script type="text/javascript"> // 定义对象字面量 var makeCounter = { add : function() { this.changeBy(1); }, red : function() { this.changeBy(-1); }, value : function() { return this.num; } }; // 将原型式继承再次封装【类似工厂模式】 function createObj(o, n) { // 通过调用函数创建一个新对象 var clone = Object.create(o); // 通过给实例对象扩展属性和方法来增强这个实例对象【子类实例独有,可传参】 clone.num = n; clone.changeBy = function(val) { clone.num += val; } // 返回新对象 return clone; } // 执行操作 function event(ele, btn1, btn2, obj) { btn1.onclick = function() { obj.add(); ele.innerHTML = obj.value(); } btn2.onclick = function() { obj.red(); ele.innerHTML = obj.value(); } ele.innerHTML = obj.value(); } window.onload = function() { // 计数器 1 var Counter1 = createObj(makeCounter, 0); var add1 = document.getElementById('add1'); var red1 = document.getElementById('red1'); var demo1 = document.getElementById('demo1'); event(demo1, add1, red1, Counter1); // 计数器 2 var Counter2 = createObj(makeCounter, 0); var add2 = document.getElementById('add2'); var red2 = document.getElementById('red2'); var demo2 = document.getElementById('demo2'); event(demo2, add2, red2, Counter2); } </script> </head> <body> <h3>"Object.create()" 实现寄生式继承</h3> <hr /> <p>计数器 1</p> <input type="button" id="add1" value="自增 (+1)" /> <input type="button" id="red1" value="自减 (-1)" /> <p id="demo1"></p> <hr /> <p>计数器 2</p> <input type="button" id="add2" value="自增 (+1)" /> <input type="button" id="red2" value="自减 (-1)" /> <p id="demo2"></p> </body> </html>

    优缺点

    【原理】:在ES5中规范了 Object.create() 方法,可以用做工具函数。 【优点】:不涉及父类的构造函数,不调用父类的构造函数就能实现继承。 【缺点】:方法在函数中定义,无法得到复用;本质上还是原型链继承,只是通过工具函数进行了封装,仍然存在子类的实例共享原型对象的问题。

    六、寄生组合式继承【最理想继承】

    (1)普通寄生组合式继承

    <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title></title> <script type="text/javascript"> // 创建 object 函数(临时中转) function object(o) { // 创建子引用类型(临时) var F = function() {}; // function F() {}; // 将 o 的实例成员赋值给 F() 的原型对象 F.prototype = o; // 返回子类的一个实例 return new F(); } // 定义原型继承函数 function inheritPrototype(subType, superType) { // 创建对象【以父类原型为模板】 var protoType = object(superType.prototype); // 增强对象【弥补重写原型而失去的 constructor 属性】 protoType.constructor = subType; // 指定对象【将创建的对象赋值给子类的原型】 subType.prototype = protoType; } // 定义超级引用类型 function SuperType(n) { // 定义实例属性和方法【独立性】 this.num = n; this.changeBy = function(val) { this.num += val; } } // prototype(原型属性) 定义共享的方法 SuperType.prototype.add = function() { this.changeBy(1); }; SuperType.prototype.red = function() { this.changeBy(-1); }; // 定义子引用类型 function SubType(n) { // 借用构造函数继承父类实例属性及方法,还可以给父类的构造函数传递参数 SuperType.call(this, n); // 可新增实例属性和方法【独立性】 } // 子类 SubType 继承超类 SuperType inheritPrototype(SubType, SuperType); // 在新原型上,新增共享的方法(先继承,再新增)【可用父类原型定义,也可在子类原型新增】 SubType.prototype.value = function() { return this.num; }; //执行操作 function event(ele, btn1, btn2, obj) { btn1.onclick = function() { obj.add(); ele.innerHTML = obj.value(); } btn2.onclick = function() { obj.red(); ele.innerHTML = obj.value(); } ele.innerHTML = obj.value(); } window.onload = function() { //计数器 1 var Counter1 = new SubType(0); var add1 = document.getElementById('add1'); var red1 = document.getElementById('red1'); var demo1 = document.getElementById('demo1'); event(demo1, add1, red1, Counter1); //计数器 2 var Counter2 = new SubType(0); var add2 = document.getElementById('add2'); var red2 = document.getElementById('red2'); var demo2 = document.getElementById('demo2'); event(demo2, add2, red2, Counter2); } </script> </head> <body> <h3>寄生组合式继承</h3> <hr /> <p>计数器 1</p> <input type="button" id="add1" value="自增 (+1)" /> <input type="button" id="red1" value="自减 (-1)" /> <p id="demo1"></p> <hr /> <p>计数器 2</p> <input type="button" id="add2" value="自增 (+1)" /> <input type="button" id="red2" value="自减 (-1)" /> <p id="demo2"></p> </body> </html>

    优缺点

    【原理】:用寄生继承来改造组合继承。 【优点】:具有组合继承的优点,同时只调用一次父类的构造函数,避免了内存和性能的浪费,消除了组合继承的缺点。 【缺点】:… 《高级程序设计》对寄生组合继承对评价:

    (2)Object.create() 寄生组合式继承

    <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title></title> <script type="text/javascript"> // 定义原型继承函数 function inheritPrototype(subType, superType) { // 创建对象【以父类原型为模板】 var protoType = Object.create(superType.prototype); // 增强对象【弥补重写原型而失去的 constructor 属性】 protoType.constructor = subType; // 指定对象【将创建的对象赋值给子类的原型】 subType.prototype = protoType; } // 定义超级引用类型 function SuperType(n) { // 定义实例属性和方法【独立性】 this.num = n; this.changeBy = function(val) { this.num += val; } } // prototype(原型属性) 定义共享的方法 SuperType.prototype.add = function() { this.changeBy(1); }; SuperType.prototype.red = function() { this.changeBy(-1); }; // 定义子引用类型 function SubType(n) { // 借用构造函数继承父类实例属性及方法,还可以给父类的构造函数传递参数 SuperType.call(this, n); // 可新增实例属性和方法【独立性】 } // 子类 SubType 继承超类 SuperType inheritPrototype(SubType, SuperType); // 在新原型上,新增共享的方法(先继承,再新增)【可用父类原型定义,也可在子类原型新增】 SubType.prototype.value = function() { return this.num; }; //执行操作 function event(ele, btn1, btn2, obj) { btn1.onclick = function() { obj.add(); ele.innerHTML = obj.value(); } btn2.onclick = function() { obj.red(); ele.innerHTML = obj.value(); } ele.innerHTML = obj.value(); } window.onload = function() { //计数器 1 var Counter1 = new SubType(0); var add1 = document.getElementById('add1'); var red1 = document.getElementById('red1'); var demo1 = document.getElementById('demo1'); event(demo1, add1, red1, Counter1); //计数器 2 var Counter2 = new SubType(0); var add2 = document.getElementById('add2'); var red2 = document.getElementById('red2'); var demo2 = document.getElementById('demo2'); event(demo2, add2, red2, Counter2); } </script> </head> <body> <h3>"Object.create()" 实现寄生组合式继承</h3> <hr /> <p>计数器 1</p> <input type="button" id="add1" value="自增 (+1)" /> <input type="button" id="red1" value="自减 (-1)" /> <p id="demo1"></p> <hr /> <p>计数器 2</p> <input type="button" id="add2" value="自增 (+1)" /> <input type="button" id="red2" value="自减 (-1)" /> <p id="demo2"></p> </body> </html>

    优缺点

    【原理】:在ES5中规范了 Object.create() 方法,可以用做工具函数。 【优点】:具有组合继承的优点,同时只调用一次父类的构造函数,避免了内存和性能的浪费,消除了组合继承的缺点。 【缺点】:… 《高级程序设计》对寄生组合继承对评价:
    Processed: 0.009, SQL: 9