JavaScript本身不提供一个 class 实现(在 ES2015/ES6 中引入了 class
关键字,但那只是语法糖 ,JavaScript 仍然是基于原型 的), 而是一种基于原型的语言 (prototype-based language) —— 同一类型的对象共有一个原型对象,对象以其原型为模板、从原型继承方法和属性。原型对象也可能拥有原型,并从中继承方法和属性,一层一层、以此类推。这种关系常被称为原型链
(prototype chain) 。
我看过许多文章,一上来就大讲prototype
、this
、原型链
等等“高深”的内容,把各种概念抛出来把人看得云里雾里、似懂非懂。而在这里,我用自己的思路和大家一起分析与学习下JS的面向对象技术。我将以面向对象的三大特性为切入点,尝试着深入了解JavaSctipr的面向对象设计思路与实现:
抽象与封装;
继承;
多态。
然后在这个探索中,我们再来着重弄清楚这几个“高级”问题:
JS的对象、函数是怎样的关系;
什么是原型(prototype) ,什么是原型链 ; 3.执行上下文 与this ;
本章中,我们将主要讨论JavaScript是如何实现面向对象编程的封装
的特性。
1 抽象与封装 我们说,封装
主要做的是两件事:抽象
和复用
。
抽象
是一种思想,讲究的是如何把现实世界映射到计算机世界中。而封装
是计算机中的实现方式,通过设计一套组合方式把基础的数据结构装配成为抽象出来的整体。
要实现这两个概念,面向对象这套编程体系定义了类
和对象
这两种结构。其中,对象
是一个个具体的客观实体,而类
是这些实体共有特征的抽象。比如,我们每个人都是具体的实体、是一个个对象;而我们人又具有共同的特性 – 都有名字、性别、都会说话等。基本的抽象模型如下:
类与对象伪代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 Class People { String name; int age; People(name, age) { this .name = name; this .age = age; } sayHi() { log('Hello,my name is ' + this .name); } } People me = new People ('Chauncey' , 30 );People peter = new People ('Peter' , 20 );
像上面的例子虽然是伪代码,但是很好理解。这里我定义了一个抽象的类 – People
表示人; 然后使用类的构造函数又定义了具体的2个对象 – me
和peter
,该定义的方法是在构造函数前加new
关键字 – new People('Chauncey', 30)
。
通过抽象
可以将客观世界映射到计算机世界中,这种思想为我们对程序设计提供了思想的基础。
而通过封装
,我们把个体及他们的属性组合了起来。 比如说我现在要构建一个学生管理系统,抽象必然是我考虑的第一步。
1.1 抽象与封装在JavaScript中的实现 那么,在JavaScript中如何实现抽象的呢?具体来说,就是在JavaScript的世界中,对象
是怎样表现的,类
又是怎样表现的。
我们先从对象入手来看:
1.2 从对象入手 首先开门见山,JavaScript是一种基于原型的语言,并没有类的概念。所以在JS中,只有对象 ,而对象其实就是一个散列表结构!
JavaScript中的对象,Object
,其实可以简单理解成“名称-值”对(而不是键值对:现在,ES 2015 的映射表(Map),比对象更接近键值对),不难联想 JavaScript 中的对象与下面这些概念类似:
Python 中的字典(Dictionary)
Perl 和 Ruby 中的散列/哈希(Hash)
C/C++ 中的散列表(Hash table)
Java 中的散列映射表(HashMap)
PHP 中的关联数组(Associative array)
这样的设计让JavaScript的对象简单灵活,能应付各类复杂需求。正因为 JavaScript 中的一切(除了核心类型,core object)都是对象,所以 JavaScript 程序必然与大量的散列表查找操作有着千丝万缕的联系,因为散列表擅长的正是高速查找。
js中,可以直接使用字面的方式定义一个对象,就像定义一个散列表那么简单。比如下面的代码:
JavaScript直接使用字面的方式定义一个对象 1 2 3 4 5 6 7 let me = { name : 'Chauncey' , age : 31 , sayHi : function ( ) { console .log ('Hello, my name is ' + this .name ); } }
像上面这样,我就定义了一个对象,而我们知道,JS中的对象其实是个散列表结构:
上面代码中对象的结构 1 2 3 4 5 6 7 8 9 ┌───────────────────────┐ ┌──────────────────────────┐ │ me │ ┌─▶│ function sayHi() │ ├────────────┬──────────┤ │ ├──────┬───────────────────┤ │ name │'Chauncey'│ │ │ f │ console.log(...) │ ├────────────┼──────────┤ │ └──────┴───────────────────┘ │ age │ 31 │ │ ├────────────┼──────────┤ │ │ sayHi() │ ─────┼──┘ └────────────┴──────────┘
上面的代码在内存中就是这样的一个结构。这样的设计很好实现,也就是当我们定义一个对象时,JS会自动为我们开辟一个散列表空间,其中表的key
是对象成员的名字,而成员的值有两种:值类型
和引用类型
,如果成员是一个基本类型(比如是数值),则直接存其值,这是值类型
。如果成员变量是指向一个方法或对象,则为引用类型
,就像C中的指针一样,它存的是一个指向所指方法或对象的引用
。
然后我们看到me
这个对象的sayHi
成员函数引用,指向的是一个sayHi()
方法,在这里不得不提一个就是javaScript中的方法与其它语言中的方法并不相同。
1.3 初探javaScript中函数与对象的关系 在JavaScript中,函数与对象的关系是比较暧昧而复杂的,后面我们会详细介绍。但在这里,我们会大概讲一下JS中的函数,暂且不必太深入,我们先不求甚解地探索下。
1.3.1 函数也是一个对象 首先,在JavaScript中,函数也是一个对象。这从我上面画的图可以看出,函数实际上也是一个散列表的组成结构。这就和我们一般熟悉的编译型语言的方法很不同了。众所周知,在编译型语言中,方法是程序的重要组成结构,我们说进程就是运行着的程序。在编译型语言中,方法会被编译成一条条命令,然后放在只读的一块内存中,我们称之为代码段。
而在JS中,函数是一个对象,也是一个散列表结构,而其内一条条的语句,实际上就是当作一条字符串,就像上图一样,我用f
表示函数的代码部分,对应的值是具体的代码。为什么可以这样?因为JavaScript是解释性语言,要运行一个方法只需要将之放入解释器就行,这就使其非常的灵活。
我们可以做个实验,如下代码示例:
下面两个定义函数的方法是一样的 1 2 3 4 5 6 7 8 9 10 11 function f1 (x, y ) { x++; return x + y; } let fn2 = new Function ('x' , 'y' , 'x++; return x + y' );console .log (fn1 (1 , 2 )); console .log (fn2 (1 , 2 ));
这里我们就看出,JS中声明一个方法其实就是定义了一个方法对象,而具体的方法体就是一段由JS表达式语句
组成的字符串。
还有一个就是 –在JavaScript中,函数也是一等公民 。
1.3.2 函数是一等公民 “一等公民 ”这个称呼起源于哪里已不好追究了,但他的意思主要就是,在JavaScript中,函数也是一个对象,而且是重要的一种一等对象
。这样设计的好处在于,你可以将函数像普通对象一样传递(作为另一个函数的参数、作为函数的返回值,或者将之随便赋值给一个变量)。
比如在JS中你可以随便定义一个函数:
1 2 3 function sayHi ( ) { return 'Hi~' ; }
你可以像定义一个字面对象一样给他添加成员变量、成员方法(这里只是意思上的区分,其实我们知道对象的散列表本质,成员变量或成员方法也好都只是添加一条key-value记录)。
1 2 3 4 sayHi.toWho = 'Peter' ; sayHi.hello = function (toWho ) { console .log ('Hello, ' + toWho); } sayHi.hello (sayHi.toWho );
这样一看,JS中function
的特性和类
的要求很像,都可以指定成员与方法。而正因为函数的这个特性,让它成为了JavaScript面向对象编程中用于实现封装
的主要工具。
1.3.3 使用函数实现类的封装 因为在JS中,函数被设计的无比强大,以至于设计者将面向对象的封装“重任”都交之于它(js语言的早期设计是一种极简风格,追求对关键字能省则省)。我们上小节说过,JS的function
和高级面向对象语言的class
很像,都可以指定成员与方法,于是可以用之来这样实现我们上面对“People”的封装:
用函数实现类的封装 1 2 3 4 5 6 7 8 9 10 11 12 13 14 function People (name, age ) { this .name = name; this .age = age; this .sayHi = function ( ) { console .log ('Hi~My name is ' + this .name ); } } let me = new People ('Chauncey' , 30 );let peter = new People ('Peter' , 20 );me.sayHi (); peter.sayHi ();
上面的代码其实很好懂,语义上就不作过多解释。有趣的是,在我们之前伪代码以及多数语言中,都是使用Class
关键词来定义一个类,而在JS中索性就用function
来定义了。这表现了JS极简的风格,当然,到ES6中还是引入了Class
关键字,但那只是语法糖,JS并没有为之增加一个Class类型。
虽然经过我们上面的学习,这个例子已很容易理解了 –这就是JS实现的抽象与封装,用函数
来实现类型的封装,定义对象实例们的通用属性与方法。基于构造函数生成具体的对象实例 。
所以,至此为止,我们已经掌握了在JavsScript中是如何实现面向对象的抽象与封装的。
但是这里我们还有会产生好奇,在JS中,new function()
会发生什么事,对象是怎样基于函数生成的?这就涉及抽象与封装的另一个重要概念 – 复用
!
2 复用 我们现在抛开上面关于抽象与封装的概念想一想,为什么要有设计“类”与“对象”这两层数据结构? – 答案就是复用
。
因为在实际的操作中,我们操作的其实都是一个个具体的对象,而之所以抽象出“类”这个概念,就是为了复用。让具体的单个对象可以省去许多一样的模板代码。比如上面的People的例子,我可以每个对象都直接使用字面定义方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 let me = { name : 'Chauncey' , age : 31 , sayHi : function ( ) { console .log ('Hello, my name is ' + this .name ); } } let Peter = { name : 'Peter' , age : 20 , sayHi : function ( ) { console .log ('Hello, my name is ' + this .name ); } }
但是这样明显会出现许多重复的定义,而且当我想去对它们的共同结构作修改时,我不得不一个个地去修改个体对象的代码。因此,我们说 – 类的存在主要是为了代码复用
。(注意这里复用有两层意义:一个是减少重复代码,一个是共用相同逻辑)
2.1 C++中类与对象的实现 我们来看看一般编译型语言中类与对象的实现。我这里用结构体举例而不是使用通用的高级语言的Class是因为一般大家都对struct的内存布局更加熟悉、其底层的理解更加简单。我们看一个例子:
1 2 3 4 5 6 7 8 9 struct People { char name[10 ]; int age; void sayHi () { } }; People *me = (struct People *)malloc (sizeof (struct People))); People *peter = (struct People *)malloc (sizeof (struct People));
这里定义的People
与上面我们用JS作的定义效果是一样的。我们都知道,结构体的定义只是一种内存布局的描述,只有当结构体实例化时,才会真正地分配内存 (正因为这个概念,所以在某些语言中,我们也常将对象称之为实例instance
)。所以我们发现在编译语言的实现中,类(比如上面示例中的struct),更像是一个模板。而根据这个模板,生成的对象实例(结构体实例)在内存中表示为:
1 2 3 4 5 6 7 8 9 ┌──────────┐ │ me │ ├──────────┤ name │'Chauncey'│ ├──────────┤ age │ 31 │ ├──────────┤ sayHi() │ 0x???? │ 函数在代码段中的地址 └──────────┘
我们看到,在C++中,实例在内存中的表示与在JS中大不相同。在JS中,变量是使用散列表
存储,根据key
来快速寻址成员变量value
。而C++中成员是使用偏移地址
来表示的(比如说你要访问name变量,编译器会根据name变量类型算出其大小与相对对象首地址的偏移),这样的好处是其对象的结构更简单了,寻址也会较散列的形式都快些。但是缺点也很明显,就是类结构与实例结构是定死的,因为使用偏移寻址
,后期想动态添加成员根本不可能。
在对比中学习可以让我们对一门学问了解的都深刻,现在我们知道,在C++中,复用是使用模板的形式来实现的,对象们共用了一份相同的内存布局,这样可以达到更高效率的操作,但是有失灵活性。
2.2 原型prototype JavaScript中对复用的实现用了另外一种思路,它抽象出来了一个共用的底层对象 – prototype
(我们常翻译为“原型 ”)。由这个底层对象来存放对象共性的东西。这样做是为什么呢?
比如我们探研下之前的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 function People (name, age ) { this .name = name; this .age = age; this .sayHi = function ( ) { console .log ('Hi~My name is ' + this .name ); } } let me = new People ('Chauncey' , 30 );let peter = new People ('Peter' , 20 );console .log (me.sayHi === peter.sayHi );
这里多说一下,咱的示例代码都可以简单地使用chrome的调试工具来测试,在最后的《小结-使用chrome调试js代码》一节中我们将附操作方法。
从上面的代码我们发现,两个对象的内部函数居然不是同一个。这当然啦,因为new People
时, function内的代码都会执行一把,在这里我们发现this.sayHi相当于都重新赋值了一遍,也就是每创建一个对象都生成了一个sayHi()
方法。
这里我们不得不又先抛出一个概念,我们new People(...)
时究竟是在干嘛?
2.2.1 所有函数都可以是构造函数 我们现在细看一个People
这个函数。有没有发现它和java或其它高级面向对象语言的中构造函数
很像!没错,JavaScript就是这么精简!在JS中,只要你对函数使用new
语句,就可以将此函数变成构造函数(注意,ES6中新增的键头函数
不可作为构造函数,只能作为普通函数
)。
为什么我要强调这个特性呢?因为JavaScript是这么精简,以至于我们要为之自己脑补许多代码。。。我们将上面People
的代码脑补一下:
脑补js构造函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 Class People { name; age; sayHi; function People (name, age) { this .name = name; this .age = age; this .sayHi = function() { console.log('Hi~My name is ' + this .name); } } }
这下对于new People()
我们就好理解了,当我们对函数作new
时,js会将之待作构造函数
,并返回为我们生成好的对象。这点和高级面向对象语言比如Java是一样的,在Java中,new ClassName(param)
也是调用该类的构造函数生成对象,并完成成员的初始化。
而在JS中并没有class
的概念,但你可以更直接地将function
“升级”为构造函数。使用new
操作符和构造器函数结合来创建对象。我们现在翻回去看看People
这个构造函数的定义,在里面this
就代指你生成的对象,我们通过给this
也就是生成的对象赋于同一的属性与方法,来完成对象的初始化。这样生成出来的对象都有相同的属性与方法,所以JS创造者大概可以说,“你看,我们没有Class关键字,面向对象的抽象与封装不是也实现的好好的吗”。
这样一来你就可以发现什么了吧,在构造函数function People(name, age)
中我们对每个对象的sayhi方法都作了这样的赋值 – this.sayHi = function()
,这就是每个对象的sayHi方法都不相同的原因(他们都创建了一份新的实例)。那我们怎样解决这个问题呢?JS的解决方案就是用所有对象共享一个公共的对象结构,把公用的属性与方法放在此公共结构上 – 这个结构就是prototype(原型)
。
现在我们把话题转回来。说回prototype
。
2.2.2 prototype
、__proto__
与原型链 首先要注意下,经过上一节我们的脑补,我们知道早期JavaScript是一种极为精简的语言,其能省就省的风格造就了它连class
关键字都懒得定义。但我们还是要清楚,这后面我们所说的function
,其实大多数指的是面向对象中说的class
的概念 。
我们现在的疑问是,所有对象共享的这个公共对象结构 – prototype
– 是在何时生成的 – 答案是在函数定义的时候。每当我们使用function
定义一个函数时(注意,新增的键头函数不会生成prototype),解释器会自动地为函数生成一个prototype
对象(注意,prototype也是一个对象),并为该function增加一个成员属性prototype
指向这个对象。
现在,我们就可以将公共的属性或方法定义在它的prototype
对象上,像这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 function People (name, age ) { this .name = name; this .age = age; this .sex = 1 ; } People .prototype .sayHi = function ( ) { console .log ('Hi~My name is ' + this .name ); } People .hello = function ( ) { console .log ('Hello' ); } People .Male = 1 ;People .Female = 0 ;let me = new People ('Chauncey' , 30 );me.sayHi (); People .hello (); me.sex = People .Male ; console .log ('The sex of me is ' + ((me.sex === People .Male )? 'Male' : 'Female' ));
现在,整个对象与函数的关系如下图:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 ┌───────────────────────┐ │ function People() │◀───────────────────────────────────────┐ ├────────────┬──────────┤ ┌──────────────────────────┐ │ │ prototype │ ─────┼─────┐ │ People │ │ ├────────────┼──────────┤ ├───▶│ <<prototype>> │ │ │ Male │ 1 │ │ ├────────────┬─────────────┤ │ ├────────────┼──────────┤ │ │constructor │ ────────┼──┘ │ Female │ 0 │ │ ├────────────┼─────────────┤ ├────────────┼──────────┤ │ │ __proto__ │ Object │ │ hello │(function)│ │ │ │<<prototype>>│ └────────────┴──────────┘ │ ├────────────┼─────────────┤ | │ │ sayHi │ (function) │ me = new People() | │ └────────────┴─────────────┘ ▼ │ ┌───────────────────────┐ │ │ me │ │ ├────────────┬──────────┤ │ │ __proto__ │ ─────┼─────┘ ├────────────┼──────────┤ │ name │'Chauncey'│ ├────────────┼──────────┤ │ age │ 31 │ ├────────────┼──────────┤ │ sex │ 1 │ └────────────┴──────────┘ 我们怎么验证此图的正确性呢?按《小结》里的方法在chrome调试器里输入上述示例代码,将`People`与`me`都打印出来就知道了。
为什么调用me.sayHi()
可以找到正确的方法呢?这里又有一个重要概念,就是当使用new
生成JS对象时,解释器会自动为之添加一个__proto__
成员,其指向People
的原型 People.prototype
!!
因此,当你执行:
1 var me = new People ('Chauncey' , 31 );
JavaScript 实际上执行的是:
1 2 3 var me = new Object ();People .call (me, 'Chauncey' , 31 );me.__proto__ = People .prototype ;
而当我们访问对象的成员时,解释器会自动帮我们作向上查找,当在本对象的散列表结构中没有找到相应的引用时,会沿着其__proto__
属性找到我们所说的公共对象prototype
,到其原型对象中去找!这有点像Objective-C中的消息转发
机制。
这种机制实现了JS中的复用
特性,现在你可以想想看,无论我用People
定义多少个对象,他们都可以共用一个prototype
对象。prototype
是一个可以被所有实例对象共享的对象,称之为原型
。它是一个名叫原型链(prototype chain)
的查询链的一部分。当访问一个对象(比如People的实例me)的属性或方法时,会先在此对象已定义的成员中找,如果没找到,就会到其prototype中去找!没错,这看起来有点像继承关系的到父类中去找一样(比如sayHi
这个函数,我们先在me
这个对象的散列表中找。结果发现没找到,就顺着其__prop__
属性找到它的原型People.prototype
,最终找到sayHi()
方法并调用之)。
然后我们还要注意一点的是,me.hello();
失败了,也就是在对象中直接调用模板函数的方法是行不通的,因为变量的查找只会沿着__proto__
指向往上查找。模板函数上直接定义的方法只能通过模板函数调用,这也是很多高级语言中实现的所谓实例变量(在构造器内使用this定义的变量成员)与静态变量(在函数上直接定义的变量成员)。
2.2.3 关于prototype的oneMoreThing 我们注意到People.prototype
中有个constructor
指回了People
函数。这种设计固然给从原型出发找到其相应的模板函数提供了路径,另一方面,这种类似“循环引用”的设计也给人以原型与模板函数一一绑定的感觉。
还有一点是我们注意到我把People.prototype
的__proto__
属性也画了出来。那当然咯,因为原型也是对象,是对象就有__proto__
属性。而People.prototype
的__proto__
指向的是Object.prototype
。这是什么意思呢?
这说明所有原型都是解释器自动地使用new Object()
生成出来的~也说明了当对象的属性在原型上也找不到时,还会继承向上查找,一直找到Object原型上去!!这不符合面向对象的思想吗?在面向对象的世界中,一切对象皆是Object。我们不妨作如下验证:
1 2 3 console .log (People .prototype .__proto__ === Object .prototype ); console .log (me.__proto__ .__proto__ === Object .prototype ); console .log (me.toString ());
这里给出了验证,而且me.toString()
的调用成功也说明了一点,就是JS的成员查找可以顺着__proto__
原型链一直往上找,使得Object
的方法也可以为子类们享用。
好了,大家看到这里,想必已经对JS的面向对象编程有了个大概的了解。因为抽象和封装是面向对象的基础,我们今天了解了其抽象和封装的实现,也就是掌握了基oop的基础。这为我们接下来的学习也奠定了根基,下章我们将一起学习另一个特性,也就是继承
在JS中是如何实现的。
这里咱们来小结一下:
3 小结 3.1 内容小结
JavaScript是基于原型的面向对象语言,并没有Class的概念。面向对象的抽象与封装的实现主要是由object
、function
、prototype
三种主要结构来构建。
其中object
指的是具体的对象实例,本质上是一个散列表结构;function
本质上是构造函数,但在其内部实现了为所生成的对象定义相同属性与方法的抽象作用;而prototype
原型的出现是为了解决面向对象的复用问题,我们说这里复用有两层意义:一个是减少重复代码,一个是共用相同逻辑。
JS中函数是一个对象,也是一等公民,可以自由赋值、自由传递(作为方法的参数或返回值等)。
JS中没有class
的概念,你可以更直接地将function
“升级”为构造函数。使用new
操作符和构造器函数结合来创建对象 – new People()
。
prototype
原型是伴随着function
构造函数的定义而自动产生的,使用func.prototype
和obj.__prop__
可以直接访问到。
而正因为__prop__
的存在,产生了所谓的原型链
。当我们访问对象的成员时,如果在本对象的散列表结构中没有找到相应的引用时,解释器会自动帮我们作向上查找,沿着其__proto__
属性找到原型对象prototype
。如果还是没有,会一直沿着原型的原型一路向上找,直到找到Object.prototype
, 其__proto__
为null。
3.2 使用chrome调试js代码 随便打开chrome,按下alt + cmd + j
可以打开调试模式。
选中console
tab,这个就是调试工具的调试终端。比如我们在里面输入如下代码(使用):
1 2 3 function test ( ) { console .log ('--' ) }
然后你就可以输入test
, test.prototype
去查看对象的组成。比如我们查看test.prototype
,就可以看到输出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 test.prototype {constructor : ƒ} constructor : ƒ test () arguments : null caller : null length : 0 name : "test" prototype : {constructor : ƒ} __proto__ : ƒ () [[FunctionLocation ]]: VM30968 :1 [[Scopes ]]: Scopes [2 ] __proto__ : constructor : ƒ Object () hasOwnProperty : ƒ hasOwnProperty () isPrototypeOf : ƒ isPrototypeOf () propertyIsEnumerable : ƒ propertyIsEnumerable () toLocaleString : ƒ toLocaleString () toString : ƒ toString () valueOf : ƒ valueOf () __defineGetter__ : ƒ __defineGetter__ () __defineSetter__ : ƒ __defineSetter__ () __lookupGetter__ : ƒ __lookupGetter__ () __lookupSetter__ : ƒ __lookupSetter__ () get __proto__ : ƒ __proto__ () set __proto__ : ƒ __proto__ ()
3.3 本章相关命令与方法 js中,可以直接使用字面的方式定义一个对象,就像定义一个散列表那么简单。下面罗列了javaScript中创建对象的几个方法:
使用new关键字;
创建字面值对象;
使用Object.create(obj)基于现有对象创建对象
对象相关函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 let obj = new Object ();let obj2 = {}; var obj3 = Object .create (obj1); let obj = { name : "Carrot" , details : { color : "orange" , size : 12 }, sayHi : function (params ) { console .log ('Hi~' ); } }; obj.details .color ; obj["details" ]["size" ];
3.4 JS面向对象的抽象与封装的实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 function People (name, age ) { this .name = name; this .age = age; this .sex = 1 ; } People .prototype .sayHi = function ( ) { console .log ('Hi~My name is ' + this .name ); } People .hello = function ( ) { console .log ('Hello' ); } People .Male = 1 ;People .Female = 0 ;let me = new People ('Chauncey' , 30 );me.sayHi (); People .hello (); me.sex = People .Male ; console .log ('The sex of me is ' + ((me.sex === People .Male )? 'Male' : 'Female' ));
4 引用
《重新介绍JavaScript》- 介绍JS很好的入门材料
《JavaScript高级程序设计》- (美)(Nicholas C.Zakas)扎卡斯 学习JS挺好的入门教材
《继承与原型链》- MDN web docs 继承与原型链, 很好,讲了性能,和原型实际
[《Function函数与Object对象的关系》- 网文](https://www.cnblogs.com/nature-tao/p/9504712.html Function函数与Object对象的关系的一篇挺好的探索文章