OOP-in-JavaScript
这篇博客我们来谈谈JavaScript 面向对象编程
这是为了给期末大作业打下基础,因为我打算用react来实现前端框架。而react是OOP的典形应用之一
学习资料:
Objects
Object Literals
首先来看看声明一个类,可以用的关键词 var let const
var定义的变量,作用域是整个封闭函数,是全域的;let定义的变量,作用域是在块级或者字块中;
变量提升:不论通过var声明的变量处于当前作用于的第几行,都会提升到作用域的最顶部。 而let声明的变量不会在顶部初始化,凡是在let声明之前使用该变量都会报错(引用错误ReferenceError);
只要块级作用域内存在let,它所声明的变量就会绑定在这个区域;
let不允许在相同作用域内重复声明(报错同时使用var和let,两个let)。
const用来专门声明一个常量,它跟let一样作用于块级作用域,没有变量提升,重复声明会报错,不同的是const声明的常量不可改变,声明时必须初始化(赋值)
1 | const circle = { |
Factories 工厂模式
但是像上面那样生成一个对象的话,如果要声明很多对象(具有相同的性质),每个对象中又有很多方法的话。实在是太麻烦了,所以我们要用工厂模式创建。
对象定义规则:
冒号和属性值之间要用空格
我们把方法也当作一个对象的属性,所以定义方法的时候为 名字: function(){}
比如draw: function(){} 其实相当于function draw(){},就是在对象直接量中定义的函数的时候要用到这个写法。
1 | function createCircle(radius) { |
Constructors 构造函数
1 | function Circle(radius) { |
我们既可以用工厂模式创建对象,也可以用构造函数来创建对象,我们对两者都需要熟悉
对于构造函数创建对象这种方法,我们一定需要 new + 构造函数
对于工厂模式创建方法,直接调用创建函数即可
Constructor Property
我们看到由两种不同函数构造出来的两个对象的constructor也是不一样的,由构造函数构造出来的another对象是利用自己的构造函数Circle()
由工厂模式构造出来的circle函数,构造函数时 Object() 也就是说createCircle函数会调用new Object()并返回
Functions are Objects
比较难理解的就是JavaScript中的类是通过函数的形式存在的,可以说函数即对象。
对上面Circle的构造函数来说 ,我们显示了他几个属性,我们同样看出来了,Circle函数是通过Function()这个构造函数构造出来的
1 | function Circle(radius) { |
函数(类)的一些方法
call 和 apply
- call()和apply()的第一个实参都为this的值,即使传入的实参是原始值或者null或者undefined
- call(),第一个参数之后的所有实参是要传入待调用函数的值;
- apply()实参都放到一个数组
1 | function Circle(radius) { |
Value vs Reference Types
1 | let x = 10; |
显示如上图,我们可以看到这和python是不一样的。当我们声明两个变量x,y的时候。x和y是相互独立的
但是当我们这样写
1 | let x = {value: 10}; |
就会这样显示,这是因为什么呢?如下图
这是因为当我们声明一个对象的时候,value:10 并没有存储在这个变量x里,而是存储在一段内存当中,而变量x存储的只是那段内存的地址。所以当令y = x的时候,事实上赋值给y的是一段地址,而非值。所以导致x,y都指向了同一段内存。自然,当value的值改变的时候,x,y都跟着改变了
总结
就是:Primitives(基本类型) are copied by their Value,Objects(对象) are copied by their reference
例子
1 | let number = 10; |
从这里我们可以很清楚的看见,当调用increase(number)的时候,传入的number只是把value值赋给了函数中的number的值,但是在外面的number还是10.
如果把返回值输出的话,可以得到11,但这任然没有改变number = 10
如果我们把number换成一个对象,又会有什么变化呢?
1 | let number = {value :10}; |
我们得到这样的结果,原理如下,我们传进去一个number对象,然后再这个increase函数里把这个对象的地址付给了obj,随后让obj的value属性+1,因为obj和number指向的是同一块内存,所以在外面number.value 也会相应的变化
Adding or Removing Properties
由构造或者工厂模式创建的对象,都是动态对象,我们可以在这个对象中添加或者删除属性。
运用 . 的方法
1 | function Circle(radius) { |
运用方括号
1 | const propertyName = 'location' |
方括号的操作比点要麻烦一点,但是可以动态访问
1 | const propertyName = 'center location' |
而且当遇到属性名称中间有特殊符号或者空格的时候,不能用点来访问,这时候需要用方括号来访问
1 | delete circle[location]; |
删除的时候也只需要在方括号中写上属性名称即可
Enumerating Properties
使用 for…in..遍历,可以打印出所有对象中的 属性,方法 的索引1
2
3
4
5
6
7
8
9
10
11
12
13
14function Circle(radius) {
console.log('this',this)
this.radius = radius;
this.draw = function () {
console.log('draw')
}
}
const circle = new Circle(10);
for(let key in circle)
{
console.log(key);
}
//打印 radius和draw
如果希望直接输出索引存储的值,或者调用方法的话,可以这样写console.log(key,circle[key] );1
2
3
4for(let key in circle)
{
console.log(key,circle[key] );
}
如果只想打印属性,需要在前面利用typeof做一个判断1
2
3
4
5
6for(let key in circle)
{
if(typeof circle[key]!== 'function')
console.log(key,circle[key] );
}
//只打印属性,不打印方法
如果只想输出索引,以数组的形式呈现,那么利用Object对象的key方法1
2
3const keys = Object.keys(circle);
console.log(keys)
// 输出["radius","draw"],无法区分是属性还是方法
可以利用 in 操作符判断一个对象中是否有该属性或方法1
2if('radius' in circle)
console.log('Circle has a radius')
Abstraction
Hide the details, Show the essentials
我们应该隐藏我们不想让外界访问到的比较复杂的和细节的部分,我们只要显示我们认为必要的部分。就比如说DVD,dvd含有非常复杂的线路板,但是他只给我们几个按钮(公共接口)来操作,这就是我们要对对象做的事情
如果不这样做,外部一直调用我们对象中的方法,那么对象中的一个小小的改变,外面很多的代码都要相应的修改,这是很麻烦的事情
Private Properties and Methods
如何实现上面的目标,我们需要用私有属性和私有方法。
那么和C++中的直接放在private:中不同,和python中两根下划线或者@property也不一样,因为JavaScript当中函数和对象是一个定义,所以我们只要把 这个方法或者属性从对象中移走,把它变成函数当中的属性或者方法就可以了。
也就是说,我们只要简单的把 this.属性/方法 替换成 let 属性/方法,就能实现隐藏
在这里我们一定要用let,因为上文说过,let声明的变量或者方法只在这个语句块中实现。但是var声明的是全局变量。所以当我们使用let声明变量和方法的时候,出了这个函数,两者就失效了。这达到了我们Abstraction的目的。
1 | function Circle(radius) { |
不要把闭包和作用域混淆。作用域只是临时的,但是闭包是永恒的。
就像上面在draw方法中的x,y,每次调用draw方法的时候,x,y都会重新被创建,然后当draw结束以后,两个x,y就死掉了。
但是闭包不一样,在调用好computeOptimumlocation之后,computeOptimumlocation任然存在在函数当中 。而且他们会保持自己的状态,因为他们是draw方法的闭包
现在,在外面,是没有办法访问defaultLocation和computeOptimumlocation这两个属性和方法的!
像上面的写法也有缺陷,因为严格意义上let声明的属性和方法并不是Circle对象的成员,他们只是Circle函数内部的局部变量而已。如果从面向对象的角度,我们仍然可以称他们为Circle对象的私有成员。因为我们没有办法去修改或者读取它
Getters and Setters
那么我们怎么样能够在外面读取对象中的私有属性呢?(只读不写)
一般的,我们可以定义一个方法,然后方法中返回这个私有属性,但是这样在外面调用的时候,显得很麻烦,因为要调用一个方法,再返回一个属性。
所以我们可以用 Object.defineProperties()或者Object.defineProperty()这个方法(一个和多个属性的区别)这个方法的用处有三个参数,第一个参数是this,第二个参数是添加属性的名称,第三个参数就是一个对象,里面存放键值对, get:function(){} 和 set:function(){}
1 | function Circle(radius) { |
get就是我们读取信息的方法,那么set就是我们设置这个私有属性的方法(我们如果不希望外界修改,就不要写set了)
当我们想要显示这个信息的时候呢console.log(circle.defaultLocation)
如果我们输出的value是不合法的(比如说只输入了一个)那么就会报错。
Exercise- Stopwatch
1 | function StopWatch() { |
Prototypes
Inheritance
有两种继承方式:Classical (类继承)和prototype(原型继承)
Prototypes and Prototypical Inheritance
在JavaScript中,没有类,只有对象。那么只有对象的话如何引入继承呢?
我们就把原来的shape看作是原型(prototype),然后把属性和方法都放到这个原型中去。注意,原型其实就是一个一般的对象。每个对象(除了元对象)都会有原型
在Javascript中创建的对象直接或间接地继承自元对象(Object) ,元对象在JavaScript中是所有对象的根对象。Object对象没有原对象
在内存中,只有一个元对象
当寻找一个方法的时候,JavaScript引擎会在这个对象里找,如果找不到,就到这个对象的原型对象去找,如果还是找不到,就继续沿着原型链往上找,一直找到元对象位置。这就是原型继承的工作原理
可以看到__proto__属性都是由一个对象指向一个对象,即指向它们的原型对象(也可以理解为父对象),那么这个属性的作用是什么呢?它的作用就是当访问一个对象的属性时,如果该对象内部不存在这个属性,那么就会去它的__proto__属性所指向的那个对象(可以理解为父对象)里找,如果父对象也不存在这个属性,则继续往父对象的__proto__属性所指向的那个对象(可以理解为爷爷对象)里找,如果还没找到,则继续往上找…直到原型链顶端null(可以理解为原始人。。。),再往上找就相当于在null上取值,会报错(可以理解为,再往上就已经不是“人”的范畴了,找不到了,到此结束,null为原型链的终点),由以上这种通过__proto__属性来连接对象直到null的一条链即为我们所谓的原型链。
其实我们平时调用的字符串方法、数组方法、对象方法、函数方法等都是靠__proto__继承而来的。
Multilevel Inheritance
比如我们声明一个数组对象myArray。数组对象myArray是继承自arrayBase(数组元对象)的
我们看都在最后一行,这个数组元对象的原型对象是 ObjectBase(元对象)示意图如下:
我们如果自己写一个构造函数:
1 | function Circle(radius) { |
那么所有这个构造函数构造出来的对象,都具有同一个原型。比如这里的circle对象的原型对象就是CircleBase,CircleBase也有一个元对象,就是ObjectBase
Property Descriptors
我们虽然可以在一个对象中调用它原型对象的方法或者属性,但是我们却无法通过Objec.keys()或者 for…in…这种方法遍历 元对象 的属性。但毕竟元对象是所有对象的根对象,为什么没有办法迭代遍历呢?
1 | let person = {name:'Jason'}; |
在console中
我们发现他的enumerable属性是false,也就是说这个toString方法,是不可以被枚举的。writable说明这个方法可以被重写
我们可以对自己创造的对象的属性进行属性的定义
1 | let person = {name:'Jason'}; |
在默认情况下,所有属性都是可写可枚举可配置的
Constructor Prototypes
constructor 属性的含义就是指向该对象的构造函数,所有函数(此时看成对象了)最终的构造函数都指向Function。从上图中可以看出Function这个对象比较特殊,它的构造函数就是它自己(因为Function可以看成是一个函数,也可以是一个对象)
prototype属性,别忘了一点 , 它是函数所独有的,它是从一个函数指向一个对象。它的含义是函数的原型对象,也就是这个函数(其实所有函数都可以作为构造函数)所创建的实例的原型对象,由此可知:f1.__proto__ === Foo.prototype,它们两个完全一样。
获得对象原型的方法是调用Object对象的getPrototypeOf() 方法
Object.prototype()是所有对象的爸爸
1 | Circle.prototype === circle.__proto__ |
Prototype vs Instance Members
js中可以说函数就是类,类就是函数。
①__proto__
和constructor
属性是对象所独有的;② prototype
属性是函数所独有的。但是由于JS中函数也是一种对象,所以函数也拥有__proto__
和constructor
属性,这点是致使我们产生困惑的很大原因之一。
从上面我们已经知道要给MyClss类的本身增加方法,需要讲方法定义在MyClass这个函数内部,这样的话,每声明一个新的实例,就会将MyClass本身复制一遍,这显然不是最优的做法。
既然不能将一个类(函数)所包含的方法都定义在函数的内部,那么,如何来给一个类添加方法呢?这就需要用到函数的prototype属性了。
那prototype属性的作用又是什么呢?它的作用就是包含可以由特定类型的所有实例共享的属性和方法,也就是让该函数所实例化的对象们都可以找到公用的属性和方法。任何函数在创建的时候,其实会默认同时创建该函数的prototype对象。
所以根据prototype的属性我们知道了,虽然新创建的对象可以使用它的构造函数所指向的prototype对象的属性和方法,但不能像构造函数那样直接调用prototype对象。
简而言之,就是如果我们使用函数的prototype对象来给函数添加方法,那么在创建一个新的对象的时候,并不会复制这个函数的所有方法,而是指向了这函数的所有方法。
1 | function Circle(radius) { |
Iterating Instance and Prototype Members
Object.keys()只返回实例对象的成员
for…in .. () 返回所有可迭代可枚举的成员(在原型链上的)
hasOwnProperty(‘成员名字’),判断该成员是继承而来的还是实例本省就有的
Avoid Extending the Built-in Objects
我们不应该修改JavaScript中的 Built-in Objects,比如说在Array.prototype 或者 Object.prototype中加入新的方法或者修改原有的方法。因为以后引入的外部库可能也有相同名称的方法但是实现起来却完全不同
Don‘t modify objects you don’t OWN!
Exercise
1 | function StopWatch() { |
Prototypical Inheritance
1- Creating Your Own Prototypical Inheritance
现在比如说我们有一个圆的构造函数
1 | function Circle(radius) { |
那么如果我又想要一个Square 构造函数。又想保留这两个方法,我们是不是要重写?
其实不是,我们可以新建一个Shape 构造函数,再Shape.prototype 中添加这两个属性,然后再让Circle和Square继承Shape即可
所以在这里,我们需要用到这个函数Object.create(proto)
返回值:一个新对象,带着指定的原型对象和属性。
也就是说命令一个Circle的原型对象,让他去等于一个新的,指向ShapeBase的对象,达到了继承的功能
1 | Circle.prototype = Object.create(Shape.prototype) |
这里,我们令Shape.prototype作为Circle的原型对象,否则
Circle.prototype = Object.create(Object.prototype),直接从元对象继承过来
1 | function Shape() { |
c是一个实例,这个实例中有一个radius =1 的属性。这个实例的原型是CircleBase对象
CircleBase对象中,我们有一个draw方法。
CircleBase也是继承来的,继承自ShapeBase对象,在ShapeBase对象中有duplicate和constructor这两个方法
CircleBase也是继承来的,继承自Object对象,也就是元对象。
2- Resetting the Constructor
但是向上面那样的写法,我们发现Circle.prototype 没有了 constructor方法了 我们就没有办法通过new Circle(1)来创建一个Circle对象了
这时候如果我们这样写 new Circle.prototype.constructor(1);的话,我们发现是这样一个情况
我们看到其实是创建出了一个Shape对象来,这是因为CircleBase并没有constructor,所以他按照原型链向上去找,在ShapeBase中找到了这个constructor方法,但这个方法是Shape的构造函数。所以构造了一个Shape对象出来。
所以,在继承的时候,我们除了需要Object.create()之外,我们还需要Circle.prototype.constructor = Circle;
那么加上这句话,我们可以看到我们利用new Circle.prototype.constructor()或者直接new Circle()构造出来的,就是一个Circle对象了
3- Calling the Super Constructor
那么我如果在Shape中传入一个color,在Circle中传入一个radius,这样的话我可以直接在Circle构造函数中调用Shape() 方法吗?
这是不行的,因为我们直接调用Shape()而不写new的话,传入的color参数会直接放到window对象(全局对象)当中去,那么如果我们写 new Shape()这就是新建了一个对象,不是我们要的目的。
所以我们的要做的就是把Shape中的color值赋给this对象
利用 Shape.call(this, color) ,就可以对this对象调用Shape方法,并且把color赋值给this对象
4- Intermediate Function Inheritance
如果我们要写多级继承或者一个原型对象产生多个子对象的时候,我们会产生很多这样的代码,既不美观又容易犯错,落下
1 | Square.prototype = Object.create(Shape.prototype); |
所以我们写一个函数来封装这个继承方法
1 | function extend(Child,Parent) { |
接下来用这个函数代替刚才的继承代码就好了!
1 | extend(Circle,Shape); |
5- Method Overriding
在子类中重写基类的方法,重写一定要放在继承代码之后
1 | extend(Circle,Shape); |
如果我们想重写基类中的方法的同时也想调用基类中的方法
1 | Circle.prototype.duplicate = function () { |
6- Polymorphism多态
1 | function Shape() { |
这就是多态的作用了
7- When to Use Inheritance
不是所有的地方都需要用继承,之后还会提到Composition方法
比如这样,就发生了逻辑错误。
正确的层级应该是这样的,但是如果有很多动物,这样的写法会让代码变得脆弱
如果要用继承,做好保存在同一级,不要多层级
记住 Favor Composition over Inheritance
我们通过组合的方式,也就是定义几个基本对象,然后拿来一个对象,我们把适用于这个对象的基本对象放加给它。
听起来有点像面向函数的编程思维。
8- Mixins
我们创建了三个基本对象:canEat, can Swim, can Walk;
1 | const canEat = { |
但是这样我们还是不精简,我们可以定义一个mixin函数
1 | function mixin(target,...sources){//...sources就是说可以传入多个参数 |
9- Exercise- Prototypical Inheritance
10- Solution- Prototypical Inheritance
11- Exercise- Polymorphism
12- Solution- Polymorphism
ES6 Classes
注:
5-10小结来自React系列教程,因为是ES6的新语法,和对象也有点关系,所以我把它记录到这里,但是没有用到class
1- ES6 Classes
在ES6中,有一种创建对象和继承关系的新方法-类
但是类本质上还是函数,只是给函数披上了一层外衣
1 | class Circle{ |
现在如果我新建一个对象,如果不写new,就会报错
2- Hoisting置顶
在JavaScript中函数的声明有两种形式
1 | function sayHello(){} |
对于类来说,我们也有两种形式,类声明和类表达式,但是和函数不同的是,类声明和类表达式都不会置顶
所以不可以再类声明前实例化类
个人建议,用类声明来创建类
3- Static Methods
我们有两种方法,实例方法和静态方法
实例方法只会在实例中生效
实例方法实在类当中起作用的,而不是在类的实例当中
现成的例子,就是Math对象中的函数,就是静态函数
1 | class Circle{ |
4- The This Keyword
1 | const Circle = function () { |
同样是调用,为什么一个是Circle对象,一个就是Window对象了呢?
因为我们从一个对象c上调用方法draw(),draw()中的this是指向对象本身的。
然而通过函数调用,也就是const draw = c.draw; draw();这种方法调用的draw()的时候
我们以一种独立的方式调用函数, 这种调用,draw()中的this指向一个默认的全局变量,也就是Window对象(或者node 中的Global)
strict模式对this的影响
在JavaScript中有一个strict 模式,当我们开启这个模式的时候,JavaScript会做很多更严格的错误检查。
通过 use strict 启用
然后我们会发现,原来的Window对象变成了undefined对象。也就是说当独立调用draw()的时候,this将不再指向全局对象。他会被设置成undefined,这样会防止我们修改Window对象中的方法
1 | class Circle{ |
这时候我们会发现调用draw()还是出现undefined
因为在类的作用下,严格模式会自动启用。
5- Binding this
我们知道如果独立调用类中的函数,那么this会指向Window或者undefined
接下来我们让this无论何时都指向对象本身
1 | const person = { |
6- Arrow Functions
箭头函数非常有用
原来我们这么写一个函数
1 | const square = function(number){ |
现在我们可以这么写
如果没有 参数,那么直接 ()=>{} 即可
如果有多可参数,那么需要用括号括起来;如果只有一个参数如下图,可以省略括号
1 | const square = number =>{ |
甚至我们如果写的是单行代码,只返回一个值,我们可以这么写,(类似于python中的lambda函数)
1 | const square = number => number*number;//理解为 number goes to number*number |
再比如:利用fileter函数的时候,简直比python都要简洁。。。
1 | const jobs =[ |
7- Arrow Functions and this
1 | const person = { |
我们按照上面这种写法吗,发现this指向了Window对象
这是因为传入的匿名回调函数,是不属于任何对象的。他和person.talk()函数没关系,是一个独立的函数,所以默认this指向了全局对象WIndow
那么我们怎么让回调函数中的this指向对象person呢?
我们可以 然后在回调函数中利用箭头符号的特性,不需要在回调函数外面声明self再让回调函数指向self
直接像下面这样修改即可。箭头函数中的this,是从上面定义this的地方继承下来的
1 | const person = { |
8- Array.map Method
ES6中新引入了Array.map
当我想渲染一个列表的时候,经常要使用到map()方法
map()方法遍历列表中的每一个项,传入到某个函数当中,然后再返回每一个项(和python的map方法差不多),得到一个新的列表
利用模板格式语法,我们可以美化代码
1 | const colors = ['red','green','blue']; |
9- Object Destructuring 解构赋值
解构赋值允许你使用类似数组或对象字面量的语法将数组和对象的属性赋给各种变量。这种赋值语法极度简洁,同时还比传统的属性访问方法更为清晰。
通常来说,你很可能这样访问数组中的前三个元素:
1 | var first = someArray[0]; |
如果使用解构赋值的特性,将会使等效的代码变得更加简洁并且可读性更高:
1 | var [first, second, third] = someArray; |
想要几个写几个
10- Spread Operator
就是三个点 ...
1 | const first = [1,2,3]; |
有了这种语法,我们可以很容易的复制一个数组
1 | const clone = [...first]; |
我们也可以对对象使用这种语法
1 | const first = {name : "Mosh"}; |
1 | const clone = {...first}; |
11-1 Private Members Using Symbols
如果我们直接在constructor中定义方法或者属性,那么这个属性可以在实例中被访问
但是利用Symbol()函数来达成 私有属性这个功能(实现了一部分私有属性的功能)
Symbol 值可以由程序创建,并可以作为属性名,而且不用担心属性名冲突。调用 Symbol() 方法将创建一个新的 Symbol 类型的值,并且该值不与其它任何值相等。 Symbol() === Symbol() //False
Symbol 一旦创建后就不可更改,不能对它们设置属性(如果在严格模式下尝试这样做,你将得到一个 TypeError)。它们可以作为属性名,这时它们和字符串的属性名没有什么区别。所以我们现在就要把这个Symbol作为属性的名称
1 | const _radius = Symbol(); |
这样我们就把原来的属性名称隐藏起来了(虽然外面还是显示了symbol)但是如果我们按照这种方式定义了多个变量的话,那么他们都显示为Symbol()但是在内部他们是不相同的
11- 2 Private Members Using WeakMaps
利用ES6的新特性WeakMap(弱映射),WeakMap 的键只能是对象,值可以为任意的类型
之所以被称为弱映射,是因为键很弱,如果键没有被引用的话,那么这个键值对就会被垃圾回收机制删除,避免了内存泄漏
1 | const _radius = new WeakMap(); |
我们看到这样 radius就被隐藏起来了;如果我们想要读取这个radius的值,那么我们需要再写一个方法
1 | const _radius = new WeakMap(); |
那么我们怎么定义一个方法呢?
1 | const _radius = new WeakMap(); |
我们最好对每一个属性或者方法都建立一个WeakMap
因为如果都放在一起,代码会变得不干净
1 | const privateProps = new WeakMap(); |
12- Getters and Setters
如果我们想要把radius设置成只读属性,一种方法就是向上面那样写一个在prototype里的方法getRadius()
1 | const _radius = new WeakMap(); |
也可以用上文提到的Object.definProperty() 这样更容易操作和访问
1 | const _radius = new WeakMap(); |
但显然Object.definProperty() 也太麻烦了,ES6中的类有着更好的实现办法,直接把get 属性(){}加到prototype当中 ,这看起来像个方法,但实际上可以直接c.radius 来进行访问。
类似的,我们也可以设置set 属性(){} ;可以对radius进行赋值
ES6 中,getter和setter 变得简单多了
1 | const _radius = new WeakMap(); |
13- Inheritance
在ES6中实现继承,我们只需要简简单单的extends关键词即可
下面的move和draw都是放在原型对象中的,而不是在constructor中的
1 | class Shape { |
我们可以在Shape中加一个constructor构造函数,让每个Shape实例都有一个color属性,但是如果在Circle中加一个constructor的话,子类的构造器中必须先调用父类的构造函数,以创建一个父类的实例。我们可以用super关键字,super()中传入父类构造函数中的属性名称
如果想把自己的属性也加到构造函数中去,那么直接写就行,super 只管父类构造函数中的属性
1 | class Shape { |
14- Method Overriding
在原型继承里提过重写函数,那么在类中呢?也是直接写就行了,JavaScript编译器会从下至上寻找这个move函数
如果我想要在自类中调用父类的move函数,又想要有自己的改变,利用super.move()即可;
1 | class Shape { |
15- Exercise
16- Solution
ES6 Tooling
1- Modules
现实生活中我们不可能在一个文件中写成百上千行的脚本文件。所以我们把代码划分成很多独立的小文件,这些文件就是所谓的模块。
模块带来了很多好处。、
Maintainability
我们通过模块化增加了程序的可维护性,更加容易管理程序。
Resuse
我们通过模块化,可以在更多的程序中重用我们已经写好了的模块
Abstract
我们通过模块化, 可以隐藏模块中的细节,之向外提供必要的接口即可
2- CommonJS Modules
Circle.js
1 | // Implementation Detail |
index.js
1 | const Circle = require('./circle'); |
运行index.js, 得到了Circle with radius 10
3- ES6 Modules
Circle.js
1 | const _radius = new WeakMap(); |
index.js
1 | import {Circle} from './circle.js'; |
4- ES6 Tooling
前端工作者,需要了解
JavaScript中我们有两类工具,分别是 Transpiler 和 Bundler
Transpiler 是 Translator+Compiler 的结合,基本上就是将我们写的JavaScript代码翻译成所有浏览器都能读懂的代码,Babel就是现代JS代码中一种非常流行的转译器
Bundler 就是把很多的js文件合并成一个js文件,也就是我们说的打包。最受欢迎的就是WebPack。他会去掉所有的空行,注释,并且会简化一切名称。这样有助于优化客户请求文件的过程
5- Babel
在terminal中安装 babel
1 | cnpm install babel-cli@6.26.0 babel-core@6.26.0 babel-preset-env@1.6.1 --save-dev |
我们在index.js中写 const x = 1;
1 | //设置好以后,需要创建一个build文件夹 |
会出现以下结果
1 | ; |
6- Webpack
webpack会把所有的js文件合并