Javascript 面向对象编程(一):封装

作者: 阮一峰

日期: 2010年5月17日

学习Javascript,最难的地方是什么?

我觉得,Object(对象)最难。因为Javascript的Object模型很独特,和其他语言都不一样,初学者不容易掌握。

下面就是我的学习笔记,希望对大家学习这个部分有所帮助。我主要参考了以下两本书籍:

《面向对象的Javascript》(Object-Oriented JavaScript)

《Javascript高级程序设计(第二版)》(Professional JavaScript for Web Developers, 2nd Edition)

它们都是非常优秀的Javascript读物,推荐阅读。

笔记分成三部分。今天的第一部分是讨论"封装"(Encapsulation),后面的第二部分第三部分讨论"继承"(Inheritance)。

============================

Javascript 面向对象编程(一):封装

作者:阮一峰

Javascript是一种基于对象(object-based)的语言,你遇到的所有东西几乎都是对象。但是,它又不是一种真正的面向对象编程(OOP)语言,因为它的语法中没有class(类)。

那么,如果我们要把"属性"(property)和"方法"(method),封装成一个对象,甚至要从原型对象生成一个实例对象,我们应该怎么做呢?

一、 生成实例对象的原始模式

假定我们把猫看成一个对象,它有"名字"和"颜色"两个属性。

  var Cat = {

    name : '',

    color : ''

  }

现在,我们需要根据这个原型对象的规格(schema),生成两个实例对象。

  var cat1 = {}; // 创建一个空对象

    cat1.name = "大毛"; // 按照原型对象的属性赋值

    cat1.color = "黄色";

  var cat2 = {};

    cat2.name = "二毛";

    cat2.color = "黑色";

好了,这就是最简单的封装了,把两个属性封装在一个对象里面。但是,这样的写法有两个缺点,一是如果多生成几个实例,写起来就非常麻烦;二是实例与原型之间,没有任何办法,可以看出有什么联系。

二、 原始模式的改进

我们可以写一个函数,解决代码重复的问题。

  function Cat(name,color) {

    return {

      name:name,

      color:color

    }

  }

然后生成实例对象,就等于是在调用函数:

  var cat1 = Cat("大毛","黄色");

  var cat2 = Cat("二毛","黑色");

这种方法的问题依然是,cat1cat2之间没有内在的联系,不能反映出它们是同一个原型对象的实例。

三、 构造函数模式

为了解决从原型对象生成实例的问题,Javascript提供了一个构造函数(Constructor)模式。

所谓"构造函数",其实就是一个普通函数,但是内部使用了this变量。对构造函数使用new运算符,就能生成实例,并且this变量会绑定在实例对象上。

比如,猫的原型对象现在可以这样写,

  function Cat(name,color){

    this.name=name;

    this.color=color;

  }

我们现在就可以生成实例对象了。

  var cat1 = new Cat("大毛","黄色");

  var cat2 = new Cat("二毛","黑色");

  alert(cat1.name); // 大毛

  alert(cat1.color); // 黄色

这时cat1cat2会自动含有一个constructor属性,指向它们的构造函数。

  alert(cat1.constructor == Cat); //true

  alert(cat2.constructor == Cat); //true

Javascript还提供了一个instanceof运算符,验证原型对象与实例对象之间的关系。

  alert(cat1 instanceof Cat); //true

  alert(cat2 instanceof Cat); //true

四、构造函数模式的问题

构造函数方法很好用,但是存在一个浪费内存的问题。

请看,我们现在为Cat对象添加一个不变的属性type(种类),再添加一个方法eat(吃)。那么,原型对象Cat就变成了下面这样:

  function Cat(name,color){

    this.name = name;

    this.color = color;

    this.type = "猫科动物";

    this.eat = function(){alert("吃老鼠");};

  }

还是采用同样的方法,生成实例:

  var cat1 = new Cat("大毛","黄色");

  var cat2 = new Cat ("二毛","黑色");

  alert(cat1.type); // 猫科动物

  cat1.eat(); // 吃老鼠

表面上好像没什么问题,但是实际上这样做,有一个很大的弊端。那就是对于每一个实例对象,type属性和eat()方法都是一模一样的内容,每一次生成一个实例,都必须为重复的内容,多占用一些内存。这样既不环保,也缺乏效率。

  alert(cat1.eat == cat2.eat); //false

能不能让type属性和eat()方法在内存中只生成一次,然后所有实例都指向那个内存地址呢?回答是可以的。

五、 Prototype模式

Javascript规定,每一个构造函数都有一个prototype属性,指向另一个对象。这个对象的所有属性和方法,都会被构造函数的实例继承。

这意味着,我们可以把那些不变的属性和方法,直接定义在prototype对象上。

  function Cat(name,color){

    this.name = name;

    this.color = 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对象,因此就提高了运行效率。

  alert(cat1.eat == cat2.eat); //true

六、 Prototype模式的验证方法

为了配合prototype属性,Javascript定义了一些辅助方法,帮助我们使用它。,

6.1 isPrototypeOf()

这个方法用来判断,某个proptotype对象和某个实例之间的关系。

  alert(Cat.prototype.isPrototypeOf(cat1)); //true

  alert(Cat.prototype.isPrototypeOf(cat2)); //true

6.2 hasOwnProperty()

每个实例对象都有一个hasOwnProperty()方法,用来判断某一个属性到底是本地属性,还是继承自prototype对象的属性。

  alert(cat1.hasOwnProperty("name")); // true

  alert(cat1.hasOwnProperty("type")); // false

6.3 in运算符

in运算符可以用来判断,某个实例是否含有某个属性,不管是不是本地属性。

  alert("name" in cat1); // true

  alert("type" in cat1); // true

in运算符还可以用来遍历某个对象的所有属性。

  for(var prop in cat1) { alert("cat1["+prop+"]="+cat1[prop]); }

未完,请继续阅读这个系列的第二部分《构造函数的继承》和第三部分《非构造函数的继承》

(完)

留言(166条)

直白易懂!逐步深入~这篇对于我这种初学者很有用啊!

崇拜中。。阮兄知识面让我情何以堪啊。。。

不错的note,最近我在看pro Javascript Design Patterns这本书,三、四章的封装、继承这块讲的非常好

《javascript: the good part》中Dauglas不推荐用new的这种方法构造对象,因为如果忘记加上new,“即没有编译时警告,也没有运行时警告”。他推荐的是函数化的方法,不使用prototype。

好多年不写程序了,不过还是觉得javascript是挺复杂的。

喜欢这篇。
越来越能看出,今后,这种娓娓道来的知识描述形式,将把至今为止的,逻辑严谨机械正确但却难懂的知识描述方式打入历史的垃圾箱里。

人,是有灵性的,是非线性的,是量子性的。
至今为止所谓的“线性严谨二元逻辑性的学术描述方式”,只不过是一种违反人类本质天性的东西,必将在完成其历史使命之后,退出历史舞台。

引用fan的发言:

Dauglas不推荐用new的这种方法构造对象,因为如果忘记加上new,“即没有编译时警告,也没有运行时警告”。他推荐的是函数化的方法,不使用prototype。

虽然这个意见是正确的。但是,Douglas提出的方法,需要自己写一个函数,在函数里再使用prototype,我觉得很不符合直觉。

很简洁实用的文章,里面有不少重来没有看过的用法,如:alert("name" in cat1);
没想到还能用 in 这样做判断

非常实用易懂的文章!
最近看《Object Oriented JavaScript》,看完第六章讲12种对象封装构造的方法,彻底晕菜。。看了您的文章决定继续去读完这本书,的确是本很好的JS参考书啊

这么一段代码,ruan兄猜猜是啥结果?我觉得这是封装中最容易犯的错误
var a=function(){
//empty
}

a.prototype.var1=[1,2,3];

var b=new a();
b.var1.push(4);

var c=new a();
alert(c.var1.join(","))

引用axu的发言:

这么一段代码,ruan兄猜猜是啥结果?我觉得这是封装中最容易犯的错误

所以只能把不变的属性和方法,绑在prototype对象上,而不能把可变属性绑上去。

我始终觉得javascript是一门函数式编程语言,这种OO的封装应该只是一种临时的workaround

这时所有实例的type属性和eat()方法,其实都是一个内存地址,指向prototype对象,因此就提高了运行效率。

也增大了风险,因为一个地方改变了,其他地方都变了。和他带来的好处相比,风险更大,不应该推荐。

ruan兄应该是有点“完美主义”的偏执吧

会不会连载,如果连载的话,就跟着你学了!

引用fan的发言:

《javascript: the good part》中Dauglas不推荐用new的这种方法构造对象,因为如果忘记加上new,“即没有编译时警告,也没有运行时警告”。他推荐的是函数化的方法,不使用prototype。

这个“如果“有点苍白

js的水很深很深 特别是用js尝试模仿oo的风格

还是喜欢使用简单的 名称空间+函数

引用ethantsien的发言:

这个“如果“有点苍白

苍白与否暂且搁置,为了避免忘记加 new,最好的办法是类名首字母大写。

不错!是一篇好文章!

这篇文章绝对要顶,学js多年,像中国唯一一个能把问题讲的这么直白透彻的。

这篇文章分析得十分仔细清楚,绝对经典啊!!!!

引用axu的发言:

这么一段代码,ruan兄猜猜是啥结果?我觉得这是封装中最容易犯的错误
var a=function(){
//empty
}

a.prototype.var1=[1,2,3];

var b=new a();
b.var1.push(4);

var c=new a();
alert(c.var1.join(","))



这就涉及到对象的继承机制了. 更直白一点, 涉及到 JS 语言的变量赋值与引用的区分.

var parent = function(name){
var that = {};
that.name = name;
return that;
};

var child = function(name,age){
var that = parent(name);
that.age = age;
return that;
};

函数化不难

Ruan兄, 文章易懂,不错,Mark~~~!

第五点应该是构造函数+原型 的混合模式吧。

简单易懂,对像我一样的初学者来说很好

这么入门级的文章,被这么多人“捧”,真够汗的~~


好文章,深得深入浅出的精髓

我个人认为:javascript 只是脚本语言,它和c++或java等语言所肩负的任务不同, 因此关于“继承”等功能不是重点,无需太复杂。
我不赞同依靠javascript大量创建UI界面,构建UI的基础应该是html+css。
当然javascript也可以写得很复杂和高明,那是高手们喜欢做的事。但不代表非要这样不可。
现在的电脑设备速度越来越快,内存越来越大,无需太计较,简单易懂、安全(prototype的做法易出错,风险大,使用时要很小心。)才是重点。

Javascript是一种基于对象(object-based)的语言,你遇到的所有东西几乎都是对象。但是,它又不是一种真正的面向对象编程(OOP)语言,因为它的语法中没有class(类)。

不明白为什么这么说,没有class就不是真正的面向对象?面向对象是一种编程思想,有没有class只是一种对这种思想的具体实现与体现,但没有class也未必不是面向对象。


ecmascript规范里如是说:
ECMAScript is an object-oriented programming language for performing computations and manipulating computational objects within a host environment.

----Ecmascript262 v5 page 1

初学者看了您的文章受益匪浅,已经购买推荐的书籍正在学习中.感谢...

第5部分里的 alert(cat1.eat == cat2.eat); 是否改为===更恰当些?

看了几个星期的书,还不如看着几个例子,浅显易懂!

OOP的人看JS真的是很变恋,你说Cat.prototype.type = "猫科动物";这个东西看上去像模仿类的静态方法吧,但访问时又不是直接通过类而是通过类的实例cat1.type;。这让初学编程的人是学面向过程还是面向对向啊?

引用Yoya的发言:
OOP的人看JS真的是很变恋,你说Cat.prototype.type = "猫科动物";这个东西看上去像模仿类的静态方法吧,但访问时又不是直接通过类而是通过类的实例cat1.type;。这让初学编程的人是学面向过程还是面向对向啊?

访问时通过类或是通过类的实例不能作为判断是否面向对象的依据,其实都是访问一块公共内存而已

我的问题跟Yoya的一样 ,OOP的人看JS真的是很变恋,你说Cat.prototype.type = "猫科动物";这个东西看上去像模仿类的静态方法吧,但访问时又不是直接通过类而是通过类的实例cat1.type;搞不清楚到底是不是共享的了

循序渐进 适合学习

function Cat(name,color){
    this.name = name;
    this.color = color;
  }
  Cat.prototype.type = "猫科动物";
  Cat.prototype.eat = function(){alert("吃老鼠")};

var cat1 = new Cat("大毛","黄色");
  var cat2 = new Cat ("二毛","黑色");
  alert(cat1.type); // 猫科动物

这里两个对象的type显然不是一个内存地址,可以写一个代码跑一下啊,我试了,不再同一内存地址,如果在同一内存地址不就成了静态变量。

引用Dada的发言:

function Cat(name,color){
    this.name = name;
    this.color = color;
  }
  Cat.prototype.type = "猫科动物";
  Cat.prototype.eat = function(){alert("吃老鼠")};

var cat1 = new Cat("大毛","黄色");
  var cat2 = new Cat ("二毛","黑色");
  alert(cat1.type); // 猫科动物

这里两个对象的type显然不是一个内存地址,可以写一个代码跑一下啊,我试了,不再同一内存地址,如果在同一内存地址不就成了静态变量。

刚才我说的也不准确,下面是摘自一个博客,我感觉挺准确的,你文章里写的有点歧义,容易让人以为prototype中所定的属性是静态方法。

话说每一个方法对象被创建时,都会自动的拥有一个叫 prototype 的属性。这个属性并无什么特别之处,它和其他的属性一样可以访问,可以赋值。不过当我们用 new 关键字来创建一个对象的时候,prototype 就起作用了:它的值(也是一个对象)所包含的所有属性,都会被复制到新创建的那个对象上去。

我昨天的留言,感觉太草率了,今天又读了一些,发现博主说的还是比较准确的,虽然给我造成了一定的误解,不过博主说还是对的,我上面说的那个根本不正确。。。。

阮大哥,你这篇刚刚写出来的时候我就看过了,当时看了半天没看懂,直到今天才看懂,我是个超级初级入门者,入门了两年啊,哈哈哈,我实在是太笨了。

谢谢你,终于看明白了,而且原来是那么的浅显易懂啊

阮老大,javascript现在面相对象编程是一个大趋势啊。我搜遍了国内的网上书店都没有买到将关于javascript面相对象编程的书籍。国内的专家写的什么垃圾书,在就过时了。javascript面相对象的时代到来了。封住继承是必须的了。求javascript面相对象的书啊。。网上就您的这几篇稿子经典,跪添了。

引用Azrael的发言:

我的问题跟Yoya的一样 ,OOP的人看JS真的是很变恋,你说Cat.prototype.type = "猫科动物";这个东西看上去像模仿类的静态方法吧,但访问时又不是直接通过类而是通过类的实例cat1.type;搞不清楚到底是不是共享的了

所以,我是不喜欢这个风格。
特别是,这种风格带来的如果只是内存节约的话

文章非常好,加深理解!!!感谢作者!

引用Ruan YiFeng的发言:

所以只能把不变的属性和方法,绑在prototype对象上,而不能把可变属性绑上去。



从原型拿来的东西,只是能查询,检索到,更新不会影响到原型的值。这样理解好些?

很好懂,谢谢啦!

引用ethantsien的发言:

这个“如果“有点苍白

苍白+1

Cat.prototype.isPrototypeOf(cat1)//true——原来构造函数的原型才是与实例的原型一致,也就是说原型一致
Cat.constructor == cat1.constructor //false——不知道为什么构造函数与构造函数为什么不一致?
Cat == cat1.constructor //true

Cat和cat1都有两个子对象:constructor和prototype
cat1.constructor就是Cat
cat1.prototype就是Cat.prototype
问题是,Cat的另一个子对象Cat.constructor是什么呢?

[QUOTE]
我们现在就可以生成实例对象了。
  var cat1 = new Cat("大毛","黄色");
  var cat2 = new Cat("二毛","黑色");
  alert(cat1.name); // 大毛
  alert(cat1.color); // 黄色
这时cat1和cat2会自动含有一个constructor属性,指向它们的构造函数。
  alert(cat1.constructor == Cat); //true
  alert(cat2.constructor == Cat); //true
[/QUOTE]
----------------------------------
cat1和cat2自身是没有constructor属性的,而是通过原型链找到Cat.prototype.constructor属性

 alert(cat1.eat == cat2.eat); //false

为什么这一句我测试的结果是true?

阮哥 前面都看的懂了 但是最后一句不太明白
for(var prop in cat1) { alert("cat1["+prop+"]="+cat1[prop]); }
希望你能解释一下

果然是你写的.....

我擦,评论好多见解是错的。。。把原型链先搞清楚就不会错了

Prototype模式 还有一点没有说到,
className.Prototype.method(){
//这里不能使用构造里的私有属性
}

Prototype模式虽然能减少内存使用,提高应用性能,但是会使代码变得冗长,增加维护难度。更好的做法应该是使用“继承”的方法,将相同的属性和方法封装在另外的对象中。

console.log(cat1.type == cat2.type); 和 console.log(cat1.type === cat2.type);
返回的都是 true,求解惑

终于懂了prototype了

引用eagle的发言:

 alert(cat1.eat == cat2.eat); //false

为什么这一句我测试的结果是true?

alert(cat1.eat == cat2.eat); //false
alert(cat1.eat() == cat2.eat()); //true

真的不错的文章,经久不衰啊,这是我看过的最好的一篇讲解这个的

引用雅蠛蝶的发言:

alert(cat1.eat == cat2.eat); //false
alert(cat1.eat() == cat2.eat()); //true

可否这么理解: alert(cat1.eat == cat2.eat);比较的是方法本身,因为方法所属的对象不同,所以两者不相等; alert(cat1.eat() == cat2.eat());比较的是两个方法的返回值,所以两者相等。

刚才试了一下发现,prototype方式添加的属性在new 出来的对象中是可以修改的而且不影响别的对象和对象原型,貌似每个new出来的对象都存在prototype里面的属性,那用prototype方式添加的属性和直接在函数里面添加的属性有什么区别了。。。求解

引用Ruan YiFeng的发言:

所以只能把不变的属性和方法,绑在prototype对象上,而不能把可变属性绑上去。


虽然是10年的文章,但看起来还是有很多启发。但是关于这段代码,窃以为不是属性可不可变的问题,而是,因为[1,2,3]是一个对象,所以var1属性也只是对这个对象的一个地址引用,所以任何实例对象改变这个对象时,其它对象再去访问该属性,都是通过引用找到的内容,因此拿到的都是改变后的属性值。如果var1赋值为基本类型,则不会出现这个问题。

回头再看的时候,发现很容易就理解的,简单明了!

引用axu的发言:

这么一段代码,ruan兄猜猜是啥结果?我觉得这是封装中最容易犯的错误
var a=function(){
//empty
}

a.prototype.var1=[1,2,3];

var b=new a();
b.var1.push(4);

var c=new a();
alert(c.var1.join(","))


今天重新看了阮老师写的文章,感觉收获很大,每次在不同的水平下看,总有不同体会,看到一条有意思的评论,上面这个人的在实例化后c居然可以改变a中的数组,按理不应这样,求解答这是什么原理?

之前也一直是看到 JavaScript 面向对象这块真是头大了,其实很多地方看起来很好理解,但是如果用自己的语言去组织这一部分内容,又什么都记不得了。如果一开始先开看简单浅显的文章,然后在去看这部分就很连贯了。

prototype有一个注意点,如果是方法,可以共用内存,如果是一个像name这样的属性,还是会新开内存的。

简单明了,确是好文呀。

赞,ruanyifeng老师果然是入门级指导的大师~

略显简单吧

不太理解 函数 和 函数相等 是空间相等 还是弹出的 值相等?

简单明白,终于知道js的对象了

讲到很清楚,个人看法是,属性绑定在this上面,方法绑定在prototype上面。

我们组长推荐的你的文章,果然不错,浅显易懂,又不缺乏内涵,赞一个!

个人感觉,阮老师的博客受众是非常非常初级入门的程序员,或者说,应该是邻家大哥这种类型的,能帮一个不懂的孩子入门,或者产生兴趣,这个阶段不能深究晦涩的原理性知识。
主要是因为文字里面有大量的原理性错误,软件公司的员工或者硕士以上学历的朋友建议还是带着批判的眼光去学习比较好。
但是不得不承认,阮老师的文字功底很好,就像有个人在你旁边跟你聊天一样,容易让新手进入状态,这方面必须赞。

引用owen的发言:

今天重新看了阮老师写的文章,感觉收获很大,每次在不同的水平下看,总有不同体会,看到一条有意思的评论,上面这个人的在实例化后c居然可以改变a中的数组,按理不应这样,求解答这是什么原理?

var1是a原型是的数组 b修改了原型导致var1成了[1,2,3,4] c继承下来了,不是c可以改变a中的数组

引用redspear的发言:

prototype有一个注意点,如果是方法,可以共用内存,如果是一个像name这样的属性,还是会新开内存的。

其实,关键还是javascript这门语言入门太简单了,所以后面很多人都按自己的理解去做了,都不去看ECMA了,包括我,也包括阮老师!

重新回头看看语言权威的描述,根本没有这些疑问,人家都说的很明白。

你写的这个不就是那个javascript高级编程里面的么(没错,就是你那参考资料),也没有太大差别。其实这些 在javascript高级编程第3版写的都很详细。

对于一个跨行学编程的小白来说,您的这篇文章对我太有意义了!

写的简洁有力 通俗易懂 牛!

入门级的,很适合初学者,简单易懂!

好文,能提供一个支付宝捐款链接吗?我想donate!

老师,您的这段代码应该在name 和 color后面加上分号。很好的文章,让我一下子就理解了

/*二、 原始模式的改进
我们可以写一个函数,解决代码重复的问题。
  function Cat(name,color){
    return {
      name:name,
      color:color
    }
  }
*/

很棒的文章,感谢,茅塞顿开,看了你的文章再去看这个书更容易理解书了,因为书好晦涩

深入浅出。

好文章,太通俗易懂了!

  function Cat(name,color){

    this.name = name;

    this.color = color;

  }

  Cat.prototype.type = "猫科动物";

  Cat.prototype.eat = function(){alert(name)};
如果在eat方法中用Cat中的name啊

写的真好 浅显易懂

引用richard的发言:

这时所有实例的type属性和eat()方法,其实都是一个内存地址,指向prototype对象,因此就提高了运行效率。

也增大了风险,因为一个地方改变了,其他地方都变了。和他带来的好处相比,风险更大,不应该推荐。

ruan兄应该是有点“完美主义”的偏执吧

所以说要把不变的属性和方便绑在prototype对象上,可变的单独封装,作为私有属性或方法。

写的真好 浅显易懂

引用owen的发言:

今天重新看了阮老师写的文章,感觉收获很大,每次在不同的水平下看,总有不同体会,看到一条有意思的评论,上面这个人的在实例化后c居然可以改变a中的数组,按理不应这样,求解答这是什么原理?

这个貌似不是改变原a中的数组吧,而是数组的join方法会重新生成一个新的string。我也是初学者,不知道这样说对不对!

写js也有一年多了~对于面向对象还是一知半解,没有投入过真正的使用里面,一直搞不明白,有什么建议吗,ruan大哥

不太同意阮老师的标题“封装”,封装(encapsulation)又叫隐藏实现(Hiding the implementation)。就是只公开代码单元的对外接口,而隐藏其具体实现。我们脑子里面很容易想到的是类似java里面的Public, Protected, Private 等访问控制符,来控制成员变量的访问权限,但是这篇文章只是在讲创建一个类型,而不是讲怎么控制属性的访问权限。

写得太好了,把复杂的东西简单讲明白了!
请允许我永久收藏。

写的好,作为后端开发。对js知识了解皮毛。最近接触到js的面向对象,看到这篇文章,感觉很有兴趣

封装的意思不是这个吧?
封装,即隐藏对象的属性和实现细节,仅对外公开接口 这才对吧

非常适合入门!多谢分享!

浅显易懂,而且逐步深化,非常感谢!

我写了一篇关于Javascript中Prototype的文章,希望阮老师多多指教

引用laoguo的发言:

喜欢这篇。
越来越能看出,今后,这种娓娓道来的知识描述形式,将把至今为止的,逻辑严谨机械正确但却难懂的知识描述方式打入历史的垃圾箱里。

人,是有灵性的,是非线性的,是量子性的。
至今为止所谓的“线性严谨二元逻辑性的学术描述方式”,只不过是一种违反人类本质天性的东西,必将在完成其历史使命之后,退出历史舞台。

爲了通俗而通俗,爲了容易理解而理解,只會使得讀者同樣流於表面,自以爲理解,實際上根本不懂。一旦難度稍微加大,舊有的思維不再好用,就難以提升了。

反之一開始就接受抽象思維的訓練而不是套用舊有的思維,雖然一開始艱難,而後必會突飛猛進。

建議这位朋友不要看任何低水平的「科普」文章,直接看英文維基百科、MDN、ECMAScript 草案比較好。

真正的入門往往就是在一瞬間,過了這道坎,你就入門了。盲目地看再多低水平的文章,照樣毫無幫助。

很喜欢,谢谢写出这么好的文章

在网上找了很多相关的内容,阮老师说的真的很好,很好理解,非常感谢阮老师分享!

很好的文章,阮兄真的深入浅出。期待js方面更深的东西

真的是言简意赅,我这种菜鸟都能理解 ,感谢分享

6.1 isPrototypeOf()
这个方法用来判断,某个“proptotype”对象和某个实例之间的关系。这个单词写错了。

引用Ruan YiFeng的发言:

所以只能把不变的属性和方法,绑在prototype对象上,而不能把可变属性绑上去。



var a=function(){
//empty
}
a.prototype.var1=[1,2,3];
a.prototype.var2 = "猫科动物";
var b=new a();
var d = new a();
b.var1.push(4);
b.var2 = "昆虫";
var c=new a();
console.log(b.var2);
console.log(c.var2);
console.log(d.var2);
console.log("--------------");
console.log(c.var1.join(","));
console.log(d.var1.join(","));

不太理解,为什么var1影响了后续实例的对象,var2没有影响到呢?

@爱喝白开水:

b.var1.push(4);b.var2 = "昆虫"; 主要在这两句的区别,js寻址的规则是先在对象的本地属性下去找,如果找不到在去该对象的proto(不同浏览器的实现可能不一样)中去找,proto指向的是a的prototype。

第一句b.var1.push(4);首先在b的本地属性(也就是上文提出的hasOwnProperty相关)找,没找到,再去b的proto中去找,找到了,然后push一下,这里b.var1和a.prototype.var1引用的是同一个,修改了b.var1中的内容,也相当于修改了a.prototype.var1的内容。

b.var2 = "昆虫"; 这里不是改变了b.var2,而是新定义了b的本地属性var2,你可以用hasOwnProperty判断下,这里的var2是b的本地属性。

@爱喝白开水:

某个实例改变var1,var2都不会改变之后实例的值
那么var1怎么从[1,2,3]变成[1,2,3,4]了呢?
这里说的"改变"是指"指针(引用)"的改变
不能改变指针的值 但能改变指针的值的值
而var1的指针确实没变 都指向这个数组(指针的值)
只不过是这个数组改变了(指针的值的值)

a.prototype.var1 = [1,2,3,4];(修改指针的值,不生效)
b.var1.push(4);(修改指针的值的值,生效)


不知道你明白没 同学生 正在学JS

太棒啦,解答我不少的困惑。
另外请教一下:Cat.prototype.type = "猫科动物";改写成var type = "猫科动物";应该也是指向统一内存空间吧???

老师把高级程序设计中的内容总结起来非常好,让我很快就理解了

厉害啊!浅显易懂!

一下看懂了,表白老师( ̄3 ̄)

对我这样的小白来说很有用,啃不懂大部头,但是还得使用js做一些东西。这篇文章讲解的很清楚。

对提高自我的技术修养很是有用!

比老师讲的易理解

阮老师真是我学习的榜样,很耐心。

真的是通俗易懂

好文,豁然开朗的感觉

其实在看这篇文章前我就懂这一块了,但是还是想过来看看峰峰大神是如何讲解原型对象和构造函数的,没想到峰峰大神讲的如此通俗易懂,膜拜大神!!!

讲的很明白,谢谢大神!

引用redspear的发言:

prototype有一个注意点,如果是方法,可以共用内存,如果是一个像name这样的属性,还是会新开内存的。

这个主要是自有属性和继承属性之间的来源差异

茅塞顿开 只能说很幸运看到这篇文章

很好理解,如果阮兄能用图表示下每个内存块里面的东西就更好啦

通俗易懂 受益匪浅

还是不太明白封装在具体案例的时候怎么用

全部是JavaScript高级程序设计的内容。

@axu:

简单的理解的话,这个var1可以认为是a的静态变量。
a类的实际定义如果用java体现的话,大概是下面这样的。
JAVA --
class A{public static int[] var1 = new int[]{1,2,3};}
//本人js小白,以上理解不代表正确


@爱喝白开水:

刚才理解错了,看到这个才大致明白啊。内存地址引用值的改变...不是全局变量的意思

话说,留言部分能不能分页啊,一进来就留意到滚动条老长老长。。。。。。

为什么要滚到最下面才能留言,好麻烦

实在话,现在2017年11月7日,这篇文章对我用处还是会很大

引用ww的发言:

刚才试了一下发现,prototype方式添加的属性在new 出来的对象中是可以修改的而且不影响别的对象和对象原型,貌似每个new出来的对象都存在prototype里面的属性,那用prototype方式添加的属性和直接在函数里面添加的属性有什么区别了。。。求解

用prototype方式添加的属性,是构造函数用于继承的,也就是说,这些属性是实例化对象所继承的属性;
但是在函数内部添加的属性如:this.name = name;这种方式添加的属性是实例化对象创建后自身的直系属性。
说白了就是 继承属性和直系属性的区别。

引用wwz的发言:

实在话,现在2017年11月7日,这篇文章对我用处还是会很大

的确,看号称最适合初学者的《JavaScript编程精解》也是云里雾里,阅读了本篇后茅塞顿开。

引用shimu的发言:

直白易懂!逐步深入~这篇对于我这种初学者很有用啊!

引用anoymous的发言:

这么入门级的文章,被这么多人“捧”,真够汗的~~

大道至简,把一个自己理解清楚的内容,用别人都容易理解的语言组织起来不是一件容易的事情

谢谢教程!真是循序渐进,浅显易懂,条理非常清晰,多谢 /抱拳

function Cat(name, color) {
this.name = name;
this.color = color;
this.type = "我是猫";
this.eat = function () {
console.log("猫吃老鼠")
}
}

var cat1 = new Cat("小白", "white");
var cat2 = new Cat("小黑", "black");

console.log(cat1.type);
cat1.eat();

console.log(cat1.eat == cat2.eat);//false
console.log(cat1.type == cat2.type);//true


为什么方法是false,属性是true呢

@瓦力:

两个对象相比较的时候,如果不是指向同一个对象,就返回false。但使用prototype模式里的方法都引用的都是同一个,所以返回true

太简单了点吧。

引用陈豪的发言:

太简单了点吧。

阮大神2010年写的,你2010年在干嘛?

引用anoymous的发言:

这么入门级的文章,被这么多人“捧”,真够汗的~~

2010年的你在干什么?你只是别人学的久一些,请别用一种比别人优越的感觉评价,搞技术需要的是不断的学习

在第三点构造函数模式那里"这时cat1和cat2会自动含有一个constructor属性,指向它们的构造函数。"这句应该是cat1和cat2自己本身没有constructor属性,这个是在他们的构造函数的原型上也就是Cat.prototype上的构造函数,准确的说:应该是Cat.prototype.constructor === Cat

第6.1 行 下面有个prototype 好像有个笔误

一直对object的概念有点模糊,还是来看阮老师的博客学习

几年前初学js,当时看原型链这块看的云里雾里。就是因为讲的时候对面向对象没有一点了解。
当时的材料也只直说原型链,执行上下文 bla bla bla... 没有实际应用完全不知道在说什么。
最近项目中遇到复杂的东西需要复用,以前简单的封装不能满足需求了。
再一看阮老师的js面向对象把以前初学时用不到的js原型链、构造函数都串了起来。
真是逻辑清晰,简单易懂。

阮老师,大写的服!通俗易懂,特别好理解!

我有一个问题想请教一下:
console.log()方法 和 alert()的方法在输出cat对象的时候,
前者输出的是对象中的具体内容,后者输出的是[object object],

function catFun(name,color) {
this.name=name;
this.color=color;
}
var cat1 = new catFun("张三","黄色");
console.log(cat1);
alert(cat1);
为什么输出的是这样子?


我查到的资料是alert()输出的是string类型,而console.log()是任何类型,
执行console.log(cat1.toString())时,对象转字符串的时候他会和alert()一致,不是很明白这一步为什么可以一致?
希望得到解答,谢谢

看了那么多原型、原型链文章都看不懂。看了这个才看懂,那些人也不知道写的什么玩意,还敢写文章。

引用iliveido的发言:

Cat和cat1都有两个子对象:constructor和prototype
cat1.constructor就是Cat
cat1.prototype就是Cat.prototype
问题是,Cat的另一个子对象Cat.constructor是什么呢?

cat1有prototype吗?你打印看看。

@axu:

function Animal(){
this.superType="Animal";
Animal.prototype.arguments=[1,2,3]
var arr=Animal.prototype.arguments.push(4)
console.log(arr)
console.log(Animal.prototype)
}
var Dog=new Animal(
)

这样就可以处理你说的那个问题了

引用jaclon的发言:

简单易懂,对像我一样的初学者来说很好

这篇文章确实通俗易懂,有种茅塞顿开的感觉

function F(){}
F.prototype.name = 'jy'
var _obj = new F()
_obj.name 此时name属性是调用的是从其构造函数F原型上继承来的 属性name

而我们现在给_obj的name属性赋值时
_obj.name = 'ly'
此时的name便是通过字面量创建对象的属性
现在的_obj具有本地name属性,_obj的原型__prop__也具有一个name属性

破局,文章写得很好。我这样的小白都感觉自己学到了很多的东西。感谢阮一峰老师

阔以撒!!!写的很明白,对于初学者学习面向对象有帮助的.............

真的牛,非常的通俗易懂

@Ruan YiFeng:

额,我运行了这段代码,结果是1,2,3,4有什么不对吗?本人小白,请不吝赐教

看了别人的文章,写了一堆字,看的云里雾里的,还是阮大神讲的浅显易懂

受益匪浅,感谢感谢!✿✿

阮一峰老师这篇文章,让我茅舍顿开

阮一峰老师很强、之前我都没好好理解过什么情况将方法写在构造方法上、什么时候将方法放在构造方法的原型上面,今天算是理解了很多很多,比如一个插件 我们构造一个插件 对象 如果有多个对象 ,但他们配置参数是不同的所以应该将参数放在构造方法当中 然后比如钩子函数、初始化方法等是这些对象共有的所以应该放在原型对象上面、这样有哪些优点?
1、这样每次创建对象时候 虽然不同的对象占用不同的内存 而且参数也不同但是 公用的同一个原型 也就是他们的钩子函数、初始化方法是共用一个的这样节省了很多内存

这时 cat1 和cat2会自动含有一个constructor属性,指向它们的构造函数。

---------------

cat1 和 cat2 中其实并没有包含 constructor 属性,它们只有一个非标准的 __proto__ 属性,该属性指向 Cat.prototype ,真正包含 construtor 属性的是 Cat.prototype.

很基础,适合小白理解

看一遍没看懂,但是直觉这已经是比较浅显易懂的object解释了,继续!

对于小白来说简单易懂,多点这种容易理解的就好了,老师厉害

十一年再看阮老师的文章,还是很适用,阮老师的文章真滴是小白入门的标准指南哈

2022了 这个2010写得内容依旧值得看,老师niu!

大佬牛,说得非常明白,不过我有个小小建议,评论区可不可加个分页器,下拉到发表看法区域得滑好久呢

我要发表看法

«-必填

«-必填,不公开

«-我信任你,不会填写广告链接