摘要:继承方式父类构造函数实例方法子类调用实现父类的构造函数吹呀吹呀,我的骄傲放纵
js构造函数
*前言:上篇文章介绍了js中通过构造函数来实例化对象的各种方法js构造函数,这篇文章主要介绍构造函数的继承(类的继承),同样包括 ES5 和 ES6 两部分的介绍,能力所限,文中难免有不合理或错误的地方,还望各位大神批评指正~
原型首先简单介绍一下实例属性/方法 和 原型属性/方法,以便更好理解下文
function Persion(name){ this.name = name; // 属性 this.setName = function(nameName){ // 实例方法 this.name = newName; } } Persion.prototype.sex = "man"; // 向 Persion 原型中追加属性(原型方法) var persion = new Persion("张三"); // 此时我们实例化一个persion对象,看一下name和sex有什么区别
在控制台查看 persion 打印如下:
原来通过 prototype 添加的属性将出现在实例对象的原型链中,
每个对象都会有一个内置 proto 对象,当在当前对象中找不到属性的时候就会在其原型链中查找(即原型链)
我们再来看下面的例子
注意:在构造函数中,一般很少有数组形式的引用属性,大部分情况都是:基本属性 + 方法。
function Animal(n) { // 声明一个构造函数 this.name = n; // 实例属性 this.arr = []; // 实例属性(引用类型) this.say = function(){ // 实例方法 return "hello world"; } } Animal.prototype.sing = function() { // 追加原型方法 return "吹呀吹呀,我的骄傲放纵~~"; } Animal.prototype.pArr = []; // 追加原型属性(引用类型)
接下来我们看一下实例属性/方法 和 原型属性/方法的区别
原型对象的用途是为每个实例对象存储共享的方法和属性,它仅仅是一个普通对象而已。并且所有的实例是共享同一个原型对象,因此有别于实例方法或属性,原型对象仅有一份。而实例有很多份,且实例属性和方法是独立的。
var cat = new Animal("cat"); // 实例化cat对象 var dog = new Animal("dog"); // 实例化狗子对象 cat.say === dog.say // false 不同的实例拥有不同的实例属性/方法 cat.sing === dog.sing // true 不同的实例共享相同的原型属性/方法 cat.arr.push("zz"); // 向cat实例对象的arr中追加元素;(私有) cat.pArr.push("xx"); // 向cat原型对象的pArr中追加元素;(共享) console.log(dog.arr); // 打印出 [],因为cat只改变了其私有的arr console.log(dog.pArr); // 打印出 ["xx"], 因为cat改变了与狗子(dog)共享的pArr
当然,原型属性为基本数据类型,则不会被共享
在构造函数中:为了属性(实例基本属性)的私有性、以及方法(实例引用属性)的复用、共享。我们提倡:
1、将属性封装在构造函数中
2、将方法定义在原型对象上
首先,我们定义一个Animal父类
function Animal(n) { this.name = n; // 实例属性 this.arr = []; // 实例属性(引用类型) this.say = function(){ // 实例方法 return "hello world"; } } Animal.prototype.sing = function() { // 追加原型方法 return "吹呀吹呀,我的骄傲放纵~~"; } Animal.prototype.pArr = []; // 追加原型属性(引用类型)1、原型链继承
function Cat(n) { this.cName = n; } Cat.prototype = new Animal(); // 父类的实例作为子类的原型对象 var tom = new Cat("tom"); // 此时Tom拥有Cat和Animal的所有实例和原型方法/属性,实现了继承 var black = new Cat("black"); tom.arr.push("Im tom"); console.log(black.arr); // 打印出 ["Im tom"], 结果其方法变成了共享的,而不是每个实例所私有的,这是因为父类的实例方法/属性变成了子类的原型方法/属性了;
优点: 实现了子对象对父对象的实例 方法/属性 和 原型方法/属性 的继承;
缺点: 子类实例共享了父类构造函数的引用数据类型属性。
function Cat(n) { this.cName = n; Animal.call(this, this.cName); // 核心,把父类的实例方法属性指向子类 } var tom = new Cat("tom"); // 此时Tom拥有Cat和Animal的所有实例和原型方法/属性,实现了继承 var black = new Cat("black"); tom.arr.push("Im tom"); console.log(black.arr); // 打印出 [], 其方法和属性是每个子类实例所私有的; tom.sing(); // undefind 无法继承父类的原型属性及方法;
优点:
1、实现了子对象对父对象的实例 方法/属性 的继承,每个子类实例所继承的父类实例方法和属性都是其私有的;
2、 创建子类实例,可以向父类构造函数传参数;
缺点: 子类实例不能继承父类的构造属性和方法;
function Cat(n) { this.cName = n; Animal.call(this, this.cName); // 核心,把父类的实例方法属性指向子类 } Cat.prototype = new Parent() // 核心, 父类的实例作为子类的原型对象 Cat.prototype.constructor = Cat; // 修复子类Cat的构造器指向,防止原型链的混乱 tom.arr.push("Im tom"); console.log(black.arr); // 打印出 [], 其方法和属性是每个子类实例所私有的; tom.sing(); // 打印出 "吹呀吹呀,我的骄傲放纵~~"; 子类继承了父类的原型方法及属性
优点:
1、创建子类实例,可以向父类构造函数传参数;
2、父类的实例方法定义在父类的原型对象上,可以实现方法复用;
3、不共享父类的构造方法及属性;
缺点: 调用了2次父类的构造方法
function Cat(n) { this.cName = n; Animal.call(this, this.cName); // 核心,把父类的实例方法属性指向子类 } Cat.prototype = Parent.prototype; // 核心, 将父类原型赋值给子类原型(子类原型和父类原型,实质上是同一个) Cat.prototype.constructor = Cat; // 修复子类Cat的构造器指向,防止原型链的混乱 tom.arr.push("Im tom"); console.log(black.arr); // 打印出 [], 其方法和属性是每个子类实例所私有的; tom.sing(); // 打印出 "吹呀吹呀,我的骄傲放纵~~"; 子类继承了父类的原型方法及属性 tom.pArr.push("publish"); // 修改继承于父类原型属性值 pArr; console.log(black.pArr); // 打印出 ["publish"], 父类的原型属性/方法 依旧是共享的, // 至此简直是完美呀~~~ 然鹅! Cat.prototype.childrenProp = "我是子类的原型属性!"; var parent = new Animal("父类"); console.log(parent.childrenProp); // 打印出"我是子类的原型属性!" what? 父类实例化的对象拥有子类的原型属性/方法,这是因为父类和子类使用了同一个原型
优点:
1、创建子类实例,可以向父类构造函数传参数;
2、子类的实例不共享父类的构造方法及属性;
3、只调用了1次父类的构造方法;
缺点: 父类和子类使用了同一个原型,导致子类的原型修改会影响父类;
function Cat(n) { this.cName = n; Animal.call(this, this.cName); // 核心,把父类的实例方法属性指向子类; } var F = function(){}; // 核心,利用空对象作为中介; F.prototype = Parent.prototype; // 核心,将父类的原型赋值给空对象F; Cat.prototype = new F(); // 核心,将F的实例赋值给子类; Cat.prototype.constructor = Cat; // 修复子类Cat的构造器指向,防止原型链的混乱; tom.arr.push("Im tom"); console.log(black.arr); // 打印出 [], 其方法和属性是每个子类实例所私有的; tom.sing(); // 打印出 "吹呀吹呀,我的骄傲放纵~~"; 子类继承了父类的原型方法及属性; tom.pArr.push("publish"); // 修改继承于父类原型属性值 pArr; console.log(black.pArr); // 打印出 ["publish"], 父类的原型属性/方法 依旧是共享的; Cat.prototype.childrenProp = "我是子类的原型属性!"; var parent = new Animal("父类"); console.log(parent.childrenProp); // undefind 父类实例化的对象不拥有子类的原型属性/方法;
优点: 完美实现继承;
缺点:实现相对复杂
function extend(Child, Parent) { var F = function(){}; F.prototype = Parent.prototype; hild.prototype = new F(); Child.prototype.constructor = Child; Child.uber = Parent.prototype; } // 使用 extend(Cat,Animal);
Child.uber = Parent.prototype; 的意思是为子对象设一个uber属性,这个属性直接指向父对象的prototype属性。(uber是一个德语词,意思是"向上"、"上一层"。)这等于在子对象上打开一条通道,可以直接调用父对象的方法。这一行放在这里,只是为了实现继承的完备性,纯属备用性质。
ES6继承方式class Animal{ // 父类 constructor(name){ // 构造函数 this.name=name; } eat(){ // 实例方法 return "hello world"; } } class Cat extends Animal{ // 子类 constructor(name){ super(name); // 调用实现父类的构造函数 this.pName = name; } sing(){ return "吹呀吹呀,我的骄傲放纵~~"; } }
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/96288.html
摘要:中创建对象的方式有很多,尤其是基于原型的方式创建对象,是理解基于原型继承的基础。该函数中的属性指向该源性对象当通过该函数的构造函数创建一个具体对象时,在这个对象中,就会有一个属性指向原型。 js中创建对象的方式有很多,尤其是基于原型的方式创建对象,是理解基于原型继承的基础。因此在这里汇总一下,并对各种方法的利弊进行总结和对比,不至于以后对这些概念有模糊。 简单方式创建 var o = ...
摘要:通过这种操作,就有了构造函数的原型对象里的方法。你也看到了,就是一个普通对象,所以这种寄生式继承适合于根据已有对象创建一个加强版的对象,在主要考虑通过已有对象来继承而不是构造函数的情况下,这种方式的确很方便。 原文地址在我的博客, 转载请注明出处,谢谢! 标签: [es5对象、原型, 原型链, 继承] 注意(这篇文章特别长)这篇文章仅仅是我个人对于JavaScript对象的理解,并不是...
摘要:使用抽象基类显示表示接口如果类的作用是定义接口,应该将其明确定义为抽象基类。此外,抽象基类可以作为其他类的唯一基类,混入类则决不能作为唯一的基类,除非这个混入类继承了另一个更具体的混入这种做法非常少见。 《流畅的Python》笔记本篇是面向对象惯用方法的第五篇,我们将继续讨论继承,重点说明两个方面:继承内置类型时的问题以及多重继承。概念比较多,较为枯燥。 1. 继承内置类型 内置类型...
摘要:对象字面量创建对象张三学生这种方式的好处显而易见,就是解决了之前的缺点。构造函数模式张三学生李四学生与之前工厂模式的方法对比变量名首字母大写了在函数内没有显式的创建及返回对象而使用了创建时使用了关键字。 面向对象是JS的重点与难点,但也是走向掌握JS的必经之路,有很多的文章或书籍中都对其进行了详细的描述,本没有必要再写这些,但是对于学习来说,讲给别人听对自己来说是一种更好的受益方式。我...
阅读 1663·2021-09-26 09:55
阅读 3677·2021-09-22 15:31
阅读 7180·2021-09-22 15:12
阅读 2186·2021-09-22 10:02
阅读 4593·2021-09-04 16:40
阅读 989·2019-08-30 15:55
阅读 2990·2019-08-30 12:56
阅读 1797·2019-08-30 12:44