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("二毛","黑色");

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

三、 构造函数模式

为了解决从原型对象生成实例的问题,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); // 黄色

这时cat1和cat2会自动含有一个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]); }

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

(完)

珠峰培训

简寻

留言(102条)

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

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

不错的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方面更深的东西

我要发表看法

«-必填

«-必填,不公开

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