看见大家都说要建设炉石传说城墙贼,为什么提示“无法

Javascript面向对象编程(二):构造函数的继承 - 阮一峰的网络日志
Javascript面向对象编程(二):构造函数的继承
这个系列的,主要介绍了如何"封装"数据和方法,以及如何从原型对象生成实例。
今天要介绍的是,对象之间的"继承"的五种方法。
比如,现在有一个"动物"对象的构造函数。
  function Animal(){
    this.species = "动物";
还有一个"猫"对象的构造函数。
  function Cat(name,color){
    this.name =
    this.color =
怎样才能使"猫"继承"动物"呢?
一、 构造函数绑定
第一种方法也是最简单的方法,使用call或apply方法,将父对象的构造函数绑定在子对象上,即在子对象构造函数中加一行:
  function Cat(name,color){
    Animal.apply(this, arguments);
    this.name =
    this.color =
  var cat1 = new Cat("大毛","黄色");
  alert(cat1.species); // 动物
二、 prototype模式
第二种方法更常见,使用prototype属性。
如果"猫"的prototype对象,指向一个Animal的实例,那么所有"猫"的实例,就能继承Animal了。
  Cat.prototype = new Animal();
  Cat.prototype.constructor = C
  var cat1 = new Cat("大毛","黄色");
  alert(cat1.species); // 动物
代码的第一行,我们将Cat的prototype对象指向一个Animal的实例。
  Cat.prototype = new Animal();
它相当于完全删除了prototype 对象原先的值,然后赋予一个新值。但是,第二行又是什么意思呢?
  Cat.prototype.constructor = C
原来,任何一个prototype对象都有一个constructor属性,指向它的构造函数。如果没有"Cat.prototype = new Animal();"这一行,Cat.prototype.constructor是指向Cat的;加了这一行以后,Cat.prototype.constructor指向Animal。
  alert(Cat.prototype.constructor == Animal); //true
更重要的是,每一个实例也有一个constructor属性,默认调用prototype对象的constructor属性。
  alert(cat1.constructor == Cat.prototype.constructor); // true
因此,在运行"Cat.prototype = new Animal();"这一行之后,cat1.constructor也指向Animal!
  alert(cat1.constructor == Animal); // true
这显然会导致继承链的紊乱(cat1明明是用构造函数Cat生成的),因此我们必须手动纠正,将Cat.prototype对象的constructor值改为Cat。这就是第二行的意思。
这是很重要的一点,编程时务必要遵守。下文都遵循这一点,即如果替换了prototype对象,
  o.prototype = {};
那么,下一步必然是为新的prototype对象加上constructor属性,并将这个属性指回原来的构造函数。
  o.prototype.constructor =
三、 直接继承prototype
第三种方法是对第二种方法的改进。由于Animal对象中,不变的属性都可以直接写入Animal.prototype。所以,我们也可以让Cat()跳过 Animal(),直接继承Animal.prototype。
现在,我们先将Animal对象改写:
  function Animal(){ }
  Animal.prototype.species = "动物";
然后,将Cat的prototype对象,然后指向Animal的prototype对象,这样就完成了继承。
  Cat.prototype = Animal.
  Cat.prototype.constructor = C
  var cat1 = new Cat("大毛","黄色");
  alert(cat1.species); // 动物
与前一种方法相比,这样做的优点是效率比较高(不用执行和建立Animal的实例了),比较省内存。缺点是 Cat.prototype和Animal.prototype现在指向了同一个对象,那么任何对Cat.prototype的修改,都会反映到Animal.prototype。
所以,上面这一段代码其实是有问题的。请看第二行
  Cat.prototype.constructor = C
这一句实际上把Animal.prototype对象的constructor属性也改掉了!
  alert(Animal.prototype.constructor); // Cat
四、 利用空对象作为中介
由于"直接继承prototype"存在上述的缺点,所以就有第四种方法,利用一个空对象作为中介。
  var F = function(){};
  F.prototype = Animal.
  Cat.prototype = new F();
  Cat.prototype.constructor = C
F是空对象,所以几乎不占内存。这时,修改Cat的prototype对象,就不会影响到Animal的prototype对象。
  alert(Animal.prototype.constructor); // Animal
我们将上面的方法,封装成一个函数,便于使用。
  function extend(Child, Parent) {
    var F = function(){};
    F.prototype = Parent.
    Child.prototype = new F();
    Child.prototype.constructor = C
    Child.uber = Parent.
使用的时候,方法如下
  extend(Cat,Animal);
  var cat1 = new Cat("大毛","黄色");
  alert(cat1.species); // 动物
这个extend函数,就是YUI库如何实现继承的方法。
另外,说明一点,函数体最后一行
  Child.uber = Parent.
意思是为子对象设一个uber属性,这个属性直接指向父对象的prototype属性。(uber是一个德语词,意思是"向上"、"上一层"。)这等于在子对象上打开一条通道,可以直接调用父对象的方法。这一行放在这里,只是为了实现继承的完备性,纯属备用性质。
五、 拷贝继承
上面是采用prototype对象,实现继承。我们也可以换一种思路,纯粹采用"拷贝"方法实现继承。简单说,如果把父对象的所有属性和方法,拷贝进子对象,不也能够实现继承吗?这样我们就有了第五种方法。
首先,还是把Animal的所有不变属性,都放到它的prototype对象上。
  function Animal(){}
  Animal.prototype.species = "动物";
然后,再写一个函数,实现属性拷贝的目的。
  function extend2(Child, Parent) {
    var p = Parent.
    var c = Child.
    for (var i in p) {
      c[i] = p[i];
      }
    c.uber =
这个函数的作用,就是将父对象的prototype对象中的属性,一一拷贝给Child对象的prototype对象。
使用的时候,这样写:
  extend2(Cat, Animal);
  var cat1 = new Cat("大毛","黄色");
  alert(cat1.species); // 动物
(本系列未完,请继续阅读第三部分。)
一、什么是内存泄漏?
程序的运行需要内存。只要程序提出要求,操作系统或者运行时(runtime)就必须供给内存。
学习函数式编程,必须掌握很多术语,否则根本看不懂文档。
本文要回答一个很重要的问题:函数式编程有什么用?
学习函数式编程的过程中,我接触到了 Ramda.js。js:面向对象编程,带你认识封装、继承和多态 - 推酷
js:面向对象编程,带你认识封装、继承和多态
本文首发于我的个人网站:
周末的时候深入的了解了下javascript的面向对象编程思想,收获颇丰,感觉对面向对象编程有了那么一丢丢的了解了~很开森
什么是面向对象编程
生动描述面向对象概念
先上一张图,可以对面向对象有一个大致的了解,然而什么是面向对象呢,用java中的一句经典语句来说就是:万事万物皆对象。面向对象的思想主要是以对象为主,将一个问题抽象出具体的对象,并且将抽象出来的对象和对象的属性和方法封装成一个类。
面向对象是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。
面向对象和面向过程的区别
面向对象和面向过程是两种不同的编程思想,我们经常会听到两者的比较,刚开始编程的时候,大部分应该都是使用的面向过程的编程,但是随着我们的成长,还是面向对象的编程思想比较好一点~
其实面向对象和面向过程并不是完全相对的,也并不是完全独立的。
我认为面向对象和面向过程的主要区别是面向过程主要是以动词为主,解决问题的方式是按照顺序一步一步调用不同的函数。
而面向对象主要是以名词为主,将问题抽象出具体的对象,而这个对象有自己的属性和方法,在解决问题的时候是将不同的对象组合在一起使用。
所以说面向对象的好处就是可扩展性更强一些,解决了代码重用性的问题。
面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。
面向对象是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。
有一个知乎的高票回答很有意思,给大家分享一下~
面向对象: 狗.吃(屎)
面向过程: 吃.(狗,屎)
具体的实现我们看一下最经典的“把大象放冰箱”这个问题
面向过程的解决方法
在面向过程的编程方式中实现“把大象放冰箱”这个问题答案是耳熟能详的,一共分三步:
开门(冰箱);
装进(冰箱,大象);
关门(冰箱)。 面向对象的解决方法
冰箱.开门()
冰箱.装进(大象)
冰箱.关门()
可以看出来面向对象和面向过程的侧重点是不同的,面向过程是以动词为主,完成一个事件就是将不同的动作函数按顺序调用。
面向对象是以主谓为主。将主谓看成一个一个的对象,然后对象有自己的属性和方法。比如说,冰箱有自己的id属性,有开门的方法。然后就可以直接调用冰箱的开门方法给其传入一个参数大象就可以了。
简单的例子面向对象和面向过程的好处还不是很明显。
五子棋例子
下面是一个我认为比较能够说明两者区别的一个栗子~:
例如五子棋,面向过程的设计思路就是首先分析问题的步骤:
把上面每个步骤用分别的函数来实现,问题就解决了。
而面向对象的设计则是从另外的思路来解决问题。整个五子棋可以分为
黑白双方,这两方的行为是一模一样的
棋盘系统,负责绘制画面
第一类对象(玩家对象)负责接受用户输入,并告知第二类对象(棋盘对象)棋子布局的变化,棋盘对象接收到了棋子的i变化就要负责在屏幕上面显示出这种变化,同时利用第三类对象(规则系统)来对棋局进行判定。
可以明显地看出,面向对象是以功能来划分问题,而不是步骤。同样是绘制棋局,这样的行为在面向过程的设计中分散在了总多步骤中,很可能出现不同的绘制版本,因为通常设计人员会考虑到实际情况进行各种各样的简化。而面向对象的设计中,绘图只可能在棋盘对象中出现,从而保证了绘图的统一。
功能上的统一保证了面向对象设计的可扩展性。比如我要加入悔棋的功能,如果要改动面向过程的设计,那么从输入到判断到显示这一连串的步骤都要改动,甚至步骤之间的循序都要进行大规模调整。如果是面向对象的话,只用改动棋盘对象就行了,棋盘系统保存了黑白双方的棋谱,简单回溯就可以了,而显示和规则判断则不用顾及,同时整个对对象功能的调用顺序都没有变化,改动只是局部的。
再比如我要把这个五子棋游戏改为围棋游戏,如果你是面向过程设计,那么五子棋的规则就分布在了你的程序的每一个角落,要改动还不如重写。但是如果你当初就是面向对象的设计,那么你只用改动规则对象就可以了,五子棋和围棋的区别不就是规则吗?(当然棋盘大小好像也不一样,但是你会觉得这是一个难题吗?直接在棋盘对象中进行一番小改动就可以了。)而下棋的大致步骤从面向对象的角度来看没有任何变化。
当然,要达到改动只是局部的需要设计的人有足够的经验,使用对象不能保证你的程序就是面向对象,初学者或者很蹩脚的程序员很可能以面向对象之虚而行面向过程之实,这样设计出来的所谓面向对象的程序很难有良好的可移植性和可扩展性。
面向对象有三大特性,封装、继承和多态。对于ES5来说,没有 class 的概念,并且由于js的函数级作用域(在函数内部的变量在函数外访问不到),所以我们就可以模拟 class 的概念,在es5中,类其实就是保存了一个函数的变量,这个函数有自己的属性和方法。将属性和方法组成一个类的过程就是封装。
封装:把客观事物封装成抽象的类,隐藏属性和方法的实现细节,仅对外公开接口。
通过构造函数添加
javascript提供了一个构造函数(Constructor)模式,用来在创建对象时初始化对象。
构造函数其实就是普通的函数,只不过有以下的特点
首字母大写(建议构造函数首字母大写,即使用大驼峰命名,非构造函数首字母小写)
内部使用 this
使用 new 生成实例
通过构造函数添加属性和方法实际上也就是通过this添加的属性和方法。因为this总是指向当前对象的,所以通过this添加的属性和方法只在当前对象上添加,是该对象自身拥有的。所以我们实例化一个新对象的时候,this指向的属性和方法都会得到相应的创建,也就是会在内存中复制一份,这样就造成了内存的浪费。
function Cat(name,color){
this.name =
this.color =
this.eat = function () {
alert('吃老鼠')
生成实例:
var cat1 = new Cat('tom','red')
通过this定义的属性和方法,我们实例化对象的时候都会重新复制一份
通过原型prototype
在类上通过 this 的方式添加属性和对象会导致内存浪费的问题,我们就考虑,有什么方法可以让实例化的类所使用的方法直接使用指针指向同一个方法。于是,就想到了原型的方式
Javascript规定,每一个构造函数都有一个prototype属性,指向另一个对象。这个对象的所有属性和方法,都会被构造函数的实例继承。
也就是说,对于那些不变的属性和方法,我们可以直接将其添加在类的 prototype 对象上。
 function Cat(name,color){
    this.name =
    this.color =
  Cat.prototype.type = &猫科动物&;
  Cat.prototype.eat = function(){alert(&吃老鼠&)};
然后生成实例
var cat1 = new Cat(&大毛&,&黄色&);
  var cat2 = new Cat(&二毛&,&黑色&);
  alert(cat1.type); // 猫科动物
  cat1.eat(); // 吃老鼠
这时所有实例的 type 属性和 eat() 方法,其实都是同一个内存地址,指向 prototype 对象,因此就提高了运行效率。
在类的外部通过.语法添加
我们还可以在类的外部通过 . 语法进行添加,因为在实例化对象的时候,并不会执行到在类外部通过 . 语法添加的属性,所以实例化之后的对象是不能访问到 . 语法所添加的对象和属性的,只能通过该类访问。
三者的区别
通过构造函数、原型和 . 语法三者都可以在类上添加属性和方法。但是三者是有一定的区别的。
构造函数:通过this添加的属性和方法总是指向当前对象的,所以在实例化的时候,通过this添加的属性和方法都会在内存中复制一份,这样就会造成内存的浪费。但是这样创建的好处是即使改变了某一个对象的属性或方法,不会影响其他的对象(因为每一个对象都是复制的一份)。
原型:通过原型继承的方法并不是自身的,我们要在原型链上一层一层的查找,这样创建的好处是只在内存中创建一次,实例化的对象都会指向这个 prototype 对象,但是这样做也有弊端,因为实例化的对象的原型都是指向同一内存地址,改动其中的一个对象的属性可能会影响到其他的对象
. 语法 :在类的外部通过 . 语法创建的属性和方法只会创建一次,但是这样创建的实例化的对象是访问不到的,只能通过类的自身访问
javascript也有private public protected
对于java程序员来说private public protected这三个关键字应该是很熟悉的哈,但是在js中,并没有类似于private public protected这样的关键字,但是我们又希望我们定义的属性和方法有一定的访问限制,于是我们就可以模拟private public protected这些访问权限。
不熟悉java的小伙伴可能不太清楚private public protected概念(其他语言我也不清楚有没有哈,但是应该都是类似的~),先来科普一下小知识点~
public:public表明该数据成员、成员函数是对所有用户开放的,所有用户都可以直接进行调用
private:private表示私有,私有的意思就是除了class自己之外,任何人都不可以直接使用,私有财产神圣不可侵犯嘛,即便是子女,朋友,都不可以使用。
protected:protected对于子女、朋友来说,就是public的,可以自由使用,没有任何限制,而对于其他的外部class,protected就变成private。
js中的private
因为javascript函数级作用域的特性(在函数中定义的属性和方法外界访问不到),所以我们在函数内部直接定义的属性和方法都是私有的。
js中的public
通过new关键词实例化时,this定义的属性和变量都会被复制一遍,所以通过this定义的属性和方法就是公有的。
通过prototype创建的属性在类的实例化之后类的实例化对象也是可以访问到的,所以也是公有的。
js中的protected
在函数的内部,我们可以通过this定义的方法访问到一些类的私有属性和方法,在实例化的时候就可以初始化对象的一些属性了。
虽然很多人都已经了解了new的实质,那么我还是要再说一下new 的实质
var o = new Object()
新建一个对象o
o. __proto__ = Object.prototype 将新创建的对象的 __proto__ 属性指向构造函数的 prototype
将this指向新创建的对象
返回新对象,但是这里需要看构造函数有没有返回值,如果构造函数的返回值为基本数据类型 string,boolean,number,null,undefined ,那么就返回新对象,如果构造函数的返回值为对象类型,那么就返回这个对象类型
var Book = function (id, name, price) {
//private(在函数内部定义,函数外部访问不到,实例化之后实例化的对象访问不到)
var num = 1;
function checkId() {
console.log('private')
//protected(可以访问到函数内部的私有属性和私有方法,在实例化之后就可以对实例化的类进行初始化拿到函数的私有属性)
this.getName = function () {
console.log(id)
this.getPrice = function () {
console.log(price)
//public(实例化的之后,实例化的对象就可以访问到了~)
this.name =
this.copy = function () {
console.log('this is public')
//在Book的原型上添加的方法实例化之后可以被实例化对象继承
Book.prototype.proFunction = function () {
console.log('this is proFunction')
//在函数外部通过.语法创建的属性和方法,只能通过该类访问,实例化对象访问不到
Book.setTime = function () {
console.log('this is new time')
var book1 = new Book('111','悲惨世界','$99')
book1.getName();
// 111 getName是protected,可以访问到类的私有属性,所以实例化之后也可以访问到函数的私有属性
book1.checkId();
//报错book1.checkId is not a function
console.log(book1.id)
// undefined id是在函数内部通过定义的,是私有属性,所以实例化对象访问不到
console.log(book1.name) //name 是通过this创建的,所以在实例化的时候会在book1中复制一遍name属性,所以可以访问到
book1.copy()
//this is public
book1.proFunction();
//this is proFunction
Book.setTime();
//this is new time
book1.setTime();
//报错book1.setTime is not a function
继承:子类可以使用父类的所有功能,并且对这些功能进行扩展。继承的过程,就是从一般到特殊的过程。
其实继承都是基于以上封装方法的三个特性来实现的。
所谓的类式继承就是使用的原型的方式,将方法添加在父类的原型上,然后子类的原型是父类的一个实例化对象。
//声明父类
var SuperClass = function () {
var id = 1;
this.name = ['javascript'];
this.superValue = function () {
console.log('superValue is true');
console.log(id)
//为父类添加共有方法
SuperClass.prototype.getSuperValue = function () {
return this.superValue();
//声明子类
var SubClass = function () {
this.subValue = function () {
console.log('this is subValue ')
//继承父类
SubClass.prototype = new SuperClass() ;
//为子类添加共有方法
SubClass.prototype.getSubValue= function () {
return this.subValue()
var sub = new SubClass();
var sub2 =
SubClass();
sub.getSuperValue();
//superValue is true
sub.getSubValue();
//this is subValue
console.log(sub.id);
//undefined
console.log(sub.name);
//javascript
sub.name.push('java');
//[&javascript&]
console.log(sub2.name)
//[&javascript&, &java&]
其中最核心的一句代码是 SubClass.prototype = new SuperClass() ;
类的原型对象 prototype 对象的作用就是为类的原型添加共有方法的,但是类不能直接访问这些方法,只有将类实例化之后,新创建的对象复制了父类构造函数中的属性和方法,并将原型 __proto__ 指向了父类的原型对象。这样子类就可以访问父类的 public 和 protected 的属性和方法,同时,父类中的 private 的属性和方法不会被子类继承。
敲黑板,如上述代码的最后一段,使用类继承的方法,如果父类的构造函数中有引用类型,就会在子类中被所有实例共用,因此一个子类的实例如果更改了这个引用类型,就会影响到其他子类的实例。
提一个小问题~为什么一个子类的实例如果更改了这个引用类型,就会影响到其他子类的实例呢,在javascript中,什么是引用类型呢,引用类型和其他的类型又有什么区别呢?
构造函数继承
正式因为有了上述的缺点,才有了构造函数继承,构造函数继承的核心思想就是 SuperClass.call(this,id) ,直接改变this的指向,使通过this创建的属性和方法在子类中复制一份,因为是单独复制的,所以各个实例化的子类互不影响。但是会造成内存浪费的问题
//构造函数继承
//声明父类
function SuperClass(id) {
var name = 'javascript'
this.books=['javascript','html','css'];
this.id = id
//声明父类原型方法
SuperClass.prototype.showBooks = function () {
console.log(this.books)
//声明子类
function SubClass(id) {
SuperClass.call(this,id)
//创建第一个子类实例
var subclass1 = new SubClass(10);
var subclass2 = new SubClass(11);
console.log(subclass1.books);
console.log(subclass2.id);
console.log(subclass1.name);
//undefined
subclass2.showBooks();
组合式继承
我们先来总结一下类继承和构造函数继承的优缺点
构造函数继承
子类的原型是父类实例化的对象
SuperClass.call(this,id)
子类实例化对象的属性和方法都指向父类的原型
每个实例化的子类互不影响
子类之间可能会互相影响
所以组合式继承就是汲取两者的优点,即避免了内存浪费,又使得每个实例化的子类互不影响。
//组合式继承
//声明父类
var SuperClass = function (name) {
this.name =
this.books=['javascript','html','css']
//声明父类原型上的方法
SuperClass.prototype.showBooks = function () {
console.log(this.books)
//声明子类
var SubClass = function (name) {
SuperClass.call(this, name)
//子类继承父类(链式继承)
SubClass.prototype = new SuperClass();
//实例化子类
var subclass1 = new SubClass('java');
var subclass2 = new SubClass('php');
subclass2.showBooks();
subclass1.books.push('ios');
//[&javascript&, &html&, &css&]
console.log(subclass1.books);
//[&javascript&, &html&, &css&, &ios&]
console.log(subclass2.books);
//[&javascript&, &html&, &css&]
寄生组合继承
那么问题又来了~组合式继承的方法固然好,但是会导致一个问题,父类的构造函数会被创建两次(call()的时候一遍,new的时候又一遍),所以为了解决这个问题,又出现了寄生组合继承。
刚刚问题的关键是父类的构造函数在类继承和构造函数继承的组合形式中被创建了两遍,但是在类继承中我们并不需要创建父类的构造函数,我们只是要子类继承父类的原型即可。所以说我们先给父类的原型创建一个副本,然后修改子类 constructor 属性,最后在设置子类的原型就可以了~
//寄生式继承
function inheritObject(subClass,superClass) {
//复制一份父类的原型保存在变量中
var p = inheritObject(superClass.prototype);
//修正因为重写子类原型导致子类constructor属性被修改
p.constructor = subC
//设置子类的原型
subClass.prototype =
//定义父类
var SuperClass = function (name) {
this.name =
this.books = ['javascript','html','css']
//定义父类原型方法
SuperClass.prototype.getBooks = function () {
console.log(this.books)
//定义子类
var SubClass = function (name) {
SuperClass.call(this,name)
inheritObject(SubClass,SuperClass);
var subclass1 = new SubClass('php')
已发表评论数()
请填写推刊名
描述不能大于100个字符!
权限设置: 公开
仅自己可见
正文不准确
标题不准确
排版有问题
主题不准确
没有分页内容
图片无法显示
视频无法显示
与原文不一致javascript如何实现面向对象的封装、继承和多态_javascript吧_百度贴吧
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&签到排名:今日本吧第个签到,本吧因你更精彩,明天继续来努力!
本吧签到人数:0成为超级会员,使用一键签到本月漏签0次!成为超级会员,赠送8张补签卡连续签到:天&&累计签到:天超级会员单次开通12个月以上,赠送连续签到卡3张
关注:145,045贴子:
javascript如何实现面向对象的封装、继承和多态收藏
javascript如何实现面向对象的封装、继承和多态
通知:6-18!千盼万盼 航嘉智能排插
终于有优惠了!!进来都是活动价!!
JS是非常灵活的,只要您想,什么结构都可以做到。
封装,和面对象的话,我给你一小段代码var Box=new function(){&&
var ob=new Object();
ob.shu=1;&&
实现对象的属性
实现对象方法 ob.add=function()
}&&&返回这个对象 }面对象的话var k=new Box();k.shu=可以直接访问了k.add();也可以直接访问方法了 我都是用这种结构写的,比较舒服javascript是弱类型的低级语言没有强大的库支持,有是有一些框架,但好的用的绝对不是免费的 希望你学掉一点,我也很喜欢javascript,愿意交个朋友的话,加我QQ
登录百度帐号推荐应用

我要回帖

更多关于 炉石传说dk城墙德2017 的文章

 

随机推荐