💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
继承模式 # 原型链 new创建的对象 通过__proto__指向 其原型对象; 原型对象也具有对象固有的普遍特征,又指向原型对象的原型对象,以此类推,形成一条链条。 ``` function Shape(){ this.name = 'shape'; this.toString = function () { return this.name; } } function TwoDShape(){ this.name = '2D shape'; } function Triangle(side, height){ this.name = 'Triangle'; this.side = side; this.height = height; this.getArea = function(){ return this.side * this.height/2; } } Shape.prototype // {constructor: ƒ} constructor: ƒ Shape() __proto__: Object Shape.constructor // ƒ Function() { [native code] } TwoDShape.prototype // {constructor: ƒ} constructor:ƒ TwoDShape() __proto__:Object TwoDShape.constructor // ƒ Function() { [native code] } Triangle.prototype // {constructor: ƒ} constructor:ƒ Triangle(side, height) __proto__:Object Triangle.constructor // ƒ Function() { [native code] } // 另建一个新实体,用它覆盖对象的原型 TwoDShape.prototype = new Shape(); Triangle.prototype = new TwoDShape(); Shape.prototype // {constructor: ƒ} Shape.constructor // ƒ Function() { [native code] } TwoDShape.prototype // Shape {name: "shape", toString: ƒ} TwoDShape.constructor // ƒ Function() { [native code] } Triangle.prototype // Shape {name: "2D shape"} Triangle.constructor // ƒ Function() { [native code] } new TwoDShape().constructor new Triangle().constructor new Shape().constructor // ƒ Shape(){ // this.name = 'shape'; // this.toString = function () { // return this.name; // } // } // 将对象的prototype重写,有可能影响对象的contructor属性,记得重置! TwoDShape.prototype.constructor = TwoDShape; Triangle.prototype.constructor = Triangle; var my = new Triangle(5, 10); my.getArea(); my.toString(); my.constructor; my instanceof Triangle; my instanceof TwoDShape; my instanceof Shape; my instanceof Array; Triangle.prototype.isPrototypeOf(my) TwoDShape.prototype.isPrototypeOf(my) Shape.prototype.isPrototypeOf(my) String.prototype.isPrototypeOf(my) ``` ## 将共享属性迁移到原型中去 ``` function Shape(){ this.name = 'shape'; } ``` 用构造器创建对象时,每个实体都会有一个全新的属性,拥有独立的存储空间; ``` function Shape(){} Shape.prototype.name = 'shape'; ``` 对于不可变属性(共享属性)及共享方法来说,这样处理更有效率。 ``` function Shape(){} Shape.prototype.name='shape'; // "shape" Shape.prototype.constructor= function(){return this.name;} // ƒ (){return this.name;} function TwoDShape(){} TwoDShape.prototype.name = '2D shape'; // 先扩展 // "2D shape" TwoDShape.prototype = new Shape(); // 后继承 // Shape.constructor {} TwoDShape.prototype.constructor = TwoDShape; // ƒ TwoDShape(){} TwoDShape.prototype.name; // "shape" // 被覆盖 ``` 💗通常**先**完成相关的**继承**关系构建,**再**对原型对象进行**扩展**! ``` function Shape(){}; Shape.prototype.name = 'shape'; Shape.prototype.toString = function(){return this.name;}; function TwoDShape(){}; // 先继承 TwoDShape.prototype = new Shape(); TwoDShape.prototype.constructor = TwoDShape; // 后扩展 TwoDShape.prototype.name = '2D shape'; function Triangle(side, height){ this.side = side; this.height = height; } Triangle.prototype = new TwoDShape(); Triangle.prototype.constructor = Triangle; Triangle.prototype.name = 'triangle'; Triangle.prototype.getArea = function(){ return this.side * this.height / 2; } var my = new Triangle(5, 10); my.getArea(); // 25 my.toString(); // triangle my.hasOwnProperty('name'); // false my.hasOwnProperty('height'); // true TwoDShape.prototype.isPrototypeOf(my); // true my instanceof Shape; // true ``` # 只继承于原型 处于效率方面的考虑,应**尽可能将可重用的属性和方法添加到原型中** 原型中的代码都是可重用的 改善效率的做法: * 不要单独为继承关系创建新对象 * 尽量减少运行时方法搜索 ``` ... TwoDShape.prototype = Shape.prototype; ... Triangle.prototype = TwoDShape.prototype; ... my.toString(); // 此时查找次数相较之前的方式有变化吗? ``` toString方法只会查找两次,自身一次,Shape.prototype一次就可以了。 采用了引用传递,都指向Shape.prototype ❌副作用:子对象对原型的修改,会影响父对象及所有继承关系。如: `Triangle.prototype.name = 'triangle';` 相当于:`Shape.prototype.name = 'triangle';` ## 临时构造器——new F() 空函数f(),中介作用,打破所有属性都指向一个相同的对象。 ``` function Shape(){}; Shape.prototype.name = 'shape'; Shape.prototype.toString = function(){return this.name;}; function TwoDShape(){}; var F = function(){}; F.prototype = Shape.prototype; TwoDShape.prototype = new F(); TwoDShape.prototype.constructor = TwoDShape; TwoDShape.prototype.name = '2D shape'; function Triangle(side, height){ this.side = side; this.height = height; } var F = function(){}; F.prototype = TwoDShape.prototype; Triangle.prototype = new F(); Triangle.prototype.constructor = Triangle; Triangle.prototype.name = 'triangle'; Triangle.prototype.getArea = function(){ return this.side * this.height / 2; } var mu = new Triangle(5, 10); mu.getArea(); // 25 mu.toString(); // "triangle" var s = new Shape(); s.toString(); // "shape" ``` **围绕原型构建继承关系!** # uber——子对象访问父对象的方式 ``` function Shape(){}; Shape.prototype.name = 'shape'; Shape.prototype.toString = function(){ var result = []; // 先检查是否存在uber属性 if(this.constructor.uber){ // 调用toString() result[result.length] = this.constructor.uber.toString(); } result[result.length] = this.name; // 放进数组并返回 return result.join(', '); }; function TwoDShape(){}; var F = function(){}; F.prototype = Shape.prototype; TwoDShape.prototype = new F(); TwoDShape.prototype.constructor = TwoDShape; TwoDShape.uber = Shape.prototype; TwoDShape.prototype.name = '2D shape'; function Triangle(side, height){ this.side = side; this.height = height; } var F = function(){}; F.prototype = TwoDShape.prototype; Triangle.prototype = new F(); Triangle.prototype.constructor = Triangle; Triangle.uber = TwoDShape.prototype; // 指向父级原型 Triangle.prototype.name = 'triangle'; Triangle.prototype.getArea = function(){ return this.side * this.height / 2; } var my = new Triangle(5, 10); my.toString(); // "shape, 2D shape, triangle" ``` # 将继承部分封装成函数 ``` function extend(Child, Parent){ var F = function(){}; F.prototype = Parent.prototype; Child.prototype = new F(); Child.prototype.constructor = Child; Child.uber = Parent.prototype; } ``` # 属性拷贝 ``` 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()方法的效率要低于extend()方法,主要是前者对部分原型属性进行了重建。不过,属性查找操作会更多的停留在对象本身,从而减少了原型链上的查找。 ``` # 对象之间的继承 在对象之间进行直接属性拷贝 浅copy:拷贝对象在内存中位置的指针 ``` // 直接将现有对象的属性全部拷贝,浅copy function extendCopy(p){ var c = {}; for(var i in p){ c[i] = p[i]; } c.uber = p; return c; } // 基本对象,模板 var shape = { name: 'shape', toString: function(){ return this.name; } } // 创建新对象 var twoDee = extendCopy(shape); // 扩展 twoDee.name = '2D shape'; twoDee.toString = function(){return this.uber.toString() + ', ' + this.name;}; var triangle = extendCopy(twoDee); triangle.name = 'triangle'; triangle.getArea = function(){ return this.side * this.height /2; } // 用init()函数解决手动设置的繁琐过程 triangle.side = 5;triangle.height = 10; triangle.getArea(); // 25 triangle.toString(); // "shape, 2D shape, triangle" ``` # 深拷贝 实现方式同浅拷贝,遍历对象的属性,在遇到对象引用属性,再次调用深拷贝函数。 ``` 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 parent = { numbers: [1,2,3], letters: ['a','b','c'], obj: { prop: 1 }, bool: true } var mydeep = deepCopy(parent); mydeep.numbers.push(4,5,6); mydeep.numbers; // [1,2,3,4,5,6] parent.numbers; // [1,2,3] ``` # object() ``` function object(o){ function F(){}; F.prototype = o; return new F(); } function object(o){ var n; function F(){}; F.prototype = o; n = new F(); n.uber = o; return n; } var triangle = object(twoDee); triangle.name = 'triangle'; triangle.getArea = function(){return this.side * this.height / 2;}; ``` 原型继承:将父对象设置成子对象的原型。 # 原型继承与属性拷贝混合应用 继承,主要目标是将一些现有的功能归为己有。 * 使用原型继承的方式克隆现存对象 * 对其他对象使用属性拷贝的方式 ``` function objectPlus(o, stuff){ var n; function F(){}; F.prototype = o; n = new F(); n.uber = o; for(var i in stuff){ n[i] = stuff[i]; } return n; } ``` 对象o用于继承,对象stuff用于拷贝方法与属性。 ``` var shape = { name: 'shape', toString: function(){ return this.name; } } var twoDee = objectPlus(shape, {name: '2D shape', toString: function(){return this.uber.toString() + ', ' + this.name}}); var triangle = objectPlus(twoDee, {name: 'triangle',side: 0, height: 0, getArea: function(){return this.side * this.height / 2; }}); var my = objectPlus(triangle, {side: 4, height: 4}); my.getArea() // 8 my.toString(); // "shape, 2D shape, triangle, triangle" ``` # 多重继承 ``` // 双重循环,外层遍历传递的对象,内层复制属性 function multi(){ var n = {}, stuff, j = 0, len = arguments.length; for(j = 0; j < len; j++){ stuff = arguments[j]; for(var i in stuff){ n[i] = stuff[i]; } } return n; } var shape = { name: 'shape', toString: function(){return this.name} } var twoDee = { name: '2D shape', dimensions: 2 } // 继承多个对象 var triangle = multi(shape, twoDee, {name: 'triangle',side: 5, height: 10, getArea: function(){return this.side * this.height / 2 }}); triangle.getArea(); // 25 triangle.dimensions; // 2 triangle.toString(); // "triangle" ``` ## 混合插入 mixins 不通过子对象的继承与扩展来完成继承 # 寄生式继承 在创建对象的函数中直接吸收其他对象的功能,然后对其进行扩展并返回。 ``` function object(o){ var n; function F(){}; F.prototype = o; n = new F(); n.uber = o; return n; } var twoD = { name: '2D shape', dimensions: 2 } function triangle(s, h){ var that = object(twoD); that.name = 'triangle'; that.getArea = function(){return this.side * this.height / 2}; that.side = s; that.height = h; return that; } var t = triangle(5, 10); t.dimensions; // 2 var t2 = new triangle(5,5); t2.getArea(); // 12.5 ``` * 将twoD对象克隆进that对象; * 扩展that对象; * 返回that对象; # 构造器借用 子对象构造器通过call()或apply()方法来调用父对象的构造器。 ``` function Shape(id){ this.id = id; } Shape.prototype.name = 'shape'; Shape.prototype.toString = function(){return this.name}; function Triangle(){ Shape.apply(this, arguments); } Triangle.prototype.name = 'triangle'; var t = new Triangle(101); t.name; // "triangle" t.id; // 101 t.toString(); // "[object Object]" ``` ## 借用构造器与原型复制 ``` function extend2(Child, Parent){ var p = Parent.prototype; var c = Child.prototype; for(var i in p){ c[i] = p[i]; } c.uber = p; } function Shape(id){ this.id = id; } Shape.prototype.name = 'shape'; Shape.prototype.toString = function(){return this.name}; function Triangle(){ Shape.apply(this, arguments); } extend2(Triangle, Shape); Triangle.prototype.name = 'triangle'; var t = new Triangle(101); t.toString(); // "Triangle" t.id; // 101 typeof t.__proto__.id; // "undefined" t.uber.name; // "shape" ``` # 小结 实现继承的方法(模式) * 基于构造器工作的模式; * 基于对象工作的模式; 分类条件: * 是否使用原型; * 是否执行属性拷贝; * 两者都有(原型属性拷贝);