[toc]
### 新增的对象字面量语法
#### 成员速写
如果对象字面量初始化时,成员的值来自于一个变量,并且该成员的名称与所获得数据的变量名称相同,则可以进行简写。例如:
```js
//这是ES6之前的标准写法,其用途是根据用户输入的数据,返回一个用户数据对象的方法。
function createUser(userId, userPwd, userAge){
return {
userId: userId,
userPwd: userPwd,
userAge: userAge
}
}
//当返回的对象的每一个key与其对应的变量名称相同时,在ES6中可以这样写
function createUser(userId, userPwd, userAge){
const say = function (){
//代码块
}
return {
userId,
userPwd,
userAge,
say
}
}
//这就是ES6的成员速写方法,其功能不变,书写简化了使阅读更加清晰。
```
>在ES6中,不仅是属性和变量同名,属性与方法同名也可以进行速写。
#### 方法速写
对象字面初始化时,方法可以省略冒号和function关键字。
```js
//ES6之前定义对象中的方法是这样书写的
const obj = {
id: 'xxx',
msg: 'abcd',
print: function(){
//代码块
}
}
//在ES6中可以这样写,其效果与上面的代码完全相同。
const obj = {
id: 'xxx',
msg: 'abcd',
print(){
//代码块
}
}
```
#### 计算属性名
有的时候,初始化对象时,某些属性名可能来自某个表达式的值。在ES6中,可以使用中括号来表示该属性名是通过计算得到的。
```js
//当一个对象在初始化的时候,其属性名需要通过某个变量或表达式的返回值来确定时,可以使用如下方法
let value1 = 'name';
let value2 = '01'
const obj = {
[value1+value2]: "hahaha" , //此时该属性的属性名为'name01'
id: 'xxx',
msg: 'abcd',
print(){
//代码块
}
}
```
### Object构造函数新增的API
ES6中针对Object构造函数新增的API均为静态API,即集成到Object函数中的API。
#### Object.is方法
用来判断两个数据是否相等,基本上与严格相等(===)是一致的,除了以下两点:
1. 用is来判断两个数据时,NaN和NaN是相等的。
2. 用is来判断两个数据时,+0和-0不相等。
```js
console.log(Object.is(NaN, NaN)); //返回的是true
console.log(Object.is(+0, -0)); //返回的是false
```
#### Object.assign方法
用于混合对象,assign方法后面可以填写无限多的参数。规则就是下一个参数覆盖上一个参数的值。
```js
let obj1 = {a: 111, b: 222, c: 333};
let obj2 = {b: 555, d: 666}
//assign方法的工作原理是,将obj2的数据覆盖到obj1,并且会对obj1产生改动,然后返回obj1
let newobj = Object.assign(obj1, obj2)
//此时,newobj的值为{a: 111, b:555, c:333, d: 666},不过obj1的值也变成了{a:111, b:555, c:333}
```
>该方法并不完美,会改动原有的对象数据。所以在ES7的展开运算符出现以后。基本上不会使用该方法,而是使用下面的方法:
```js
let obj1 = {a: 111, b: 222, c: 333};
let obj2 = {b: 555, d: 666};
let newobj = {
...obj1,
...obj2
}
//这样做既可以让obj2中与obj1相同的数据覆盖掉obj1的数据,又不会改变obj1的值。
```
>若一定要使用assign方法,又不想改变obj1的值可以使用下面的方法
```js
let newobj = Object.assign ({}, obj1, obj2);
//在最前面放置一个空对象,这样就不会改动obj1的值。
```
#### Object.getOwnPropertyNames的枚举顺序
Object.getOwnPropertyNames方法在ES6之前就存在,只不过官方没有明确要求对属性的顺序如何排序。如何排序,完全由浏览器厂商决定。
ES6规定了该方法返回的数组的排序方式为:先排数字并按照升序排序,再排其他并按照书写顺序排序。
#### Object.setPrototypeOf方法
该方法用来设置某个对象的隐式原型
```js
Object.setPrototypeOf(obj1, obj2);
//该方法相当于obj1.__proto__ = obj2,将obj2的属性及方法当做obj1的隐式原型。
```
### 面向对象编程简介
1. 面向对象是什么?
面向对象是一种编程思想,与具体的语言无关
2. 面向对象与面向过程编程有什么不同?
面向过程编程:思考的切入点是功能实现的步骤。
面向对象编程:思考的切入点是对象的功能划分。
### 类:构造函数的语法糖
#### ES6之前JS传统的构造函数存在的问题
1. 属性和原型方法定义分离,降低了可读性
2. 原型成员可以被枚举
3. 默认情况下,构造函数仍然可以被当做普通函数使用
为了解决上述的问题,ES6中引入了类的概念。
#### ES6中如何创建一个类
在ES6中可以使用关键字class来创建一个类
```js
class Student { //定义类的名称
//constructor用来创建类的结构,用来定义类的属性。相当于定义构造函数
constructor(name, age, sex, classNum){
this.name = name;
this.age = age;
this.sex = sex;
this.classNum = classNum;
}
//下面用来定义类的方法,相当于传统JS在prototype上定义的方法,且定义好的方法会自动被加到原型上。
study(){
//代码块
};
run(){
//代码块
}
}
```
上面就是在ES6中定义的一个类的写法。
#### ES6中类的特点
1. 类声明不会被提升,与let和const一样,存在暂时性死区
2. 类中的所有代码均在严格模式下执行
3. 类的所有方法都是不可被枚举的
4. 类的所有方法都无法被当做构造函数使用
5. 类的构造器必须使用new来调用
#### 类的其它书写方式
1. 可计算的成员名
当类中的某个方法名,在编写过程中不确定。需要通过某个变量的值或表达式计算才能得到。可以在类中定义方法时使用[变量名/表达式]来定义。
2. getter(读取器)和setter(赋值器)
在类的创建使用过程中,某些属性在赋值时可能会需要加一些判断来确认用户输入的值是否合法。例如:上面代码中的age属性,如果用户输入'abc'就无法确定该条学生数据的年龄。或者有些时候在我们获取属性值的时候需要一些包装效果,例如:上面代码中age属性,如果想读取年龄值的时候在年龄值后面加上'岁'。为实现上面的效果,在ES6中我们可以使用如下方法:
```js
class Student { //定义类的名称
constructor(name, age, sex, classNum){
this.name = name;
this.age = age;
this.sex = sex;
this.classNum = classNum;
}
//创建一个age属性,并给其加上setter,当给该属性赋值时会执行该函数
set age(age){
if( typeof age !== 'number' ){
thrownewTypeError('输入的年龄数据类型错误')
}
if( age < 6 ){
age = 6;
}elseif( age > 13 ){
age = 13;
}
this.\_age\=age; //各项判断条件执行完后,将最终的age属性结果保存至Student类的\_age属性中。
}
//创建一个age属性,并给其加上getter,读取该属性时会执行该函数
get age(){
return this._age + '岁';
}
}
```
>getter和setter统称为访问器。
>使用getter和setter控制的属性,不在原型上
>getter不能设置参数,setter只能设置一个参数
3. 静态成员
构造函数本身的成员(属性)叫做静态成员。由new创建的实例中的成员称为实例成员。
在传统的JS构造函数定义中,静态成员的定义只能写在构造函数外面。影响了代码的结构和可读性。
在ES6中引入了一个新的关键字'static',可以在类中定义静态成员。
```js
class Student { //定义类的名称
constructor(name, age, sex, classNum){
this.name = name;
this.age = age;
this.sex = sex;
this.classNum = classNum;
}
static schoolName = 'xx小学' //schoolName就是一个静态属性。(直接给静态属性赋值是ES7中支持的方法)
static sing(){ //也可以定义一个静态的方法,该方法无需创建实例,可以直接调用。
//代码块
}
}
console.log(Student.schoolName); //返回'xx小学',该属性无需创建实例。直接可以用类来获取。
```
>静态成员在类中可以直接调用,但不会出现在实例的属性和方法中。
4. 字段初始化器(ES7更新的方法)
在编写类的过程中,我们需要某些属性初始化的时候就有值的,在ES7中我们可以用如下方法进行设置。
```js
class Student { //定义类的名称
classNum = 1; //用该类创建的实例班级都是1班
age = 8; //用该类创建的实例年龄都是8岁
//象这样固定值的属性,在ES7中可以直接在类中赋值
constructor(name, age, sex, classNum){
this.name = name;
this.sex = sex;
}
}
let std1 = new Student ('小明')
console.log(std1.age); //std1的年龄输出的是默认值8
```
- 使用static的字段初始化器,添加的是静态成员
- 没有使用static的字段初始化器,添加的成员位于对象上。(即添加的是实例属性)
- 箭头函数在字段初始化器位置上时,this指向当前对象。当类中的方法使用了this,且不想被外部调用时改变this的指向时,可以将该方法使用箭头函数进行初始化,用来固定方法中this的指向。但如果这样定义,该方法就不在类的原型上了。该方法与其它实例属性一样,是可以被枚举出的。且每创建一个实例,就会跟随实例创建一个该方法。
5. 类表达式
类也可以以表达式的形式书写,例如:
```js
const test = class {
a = 1;
b = 2
}
//此时相当于定义了一个匿名类,将类赋值给test
let p = new test();
console.log(p); //返回{a:1, b:2}
```
6. [扩展]装饰器(ES7)(Decorator)
在开发的过程中,会发生有些类中的方法因无法升级或维护,暂时停用了。但过段时间可能又想使用了。如果因为这种情况来直接删除或修改类中某个方法的代码会对整个代码的安全和稳定性产生影响。
为了解决这种问题,我们采用一种方法叫做横切关注点。在ES7中为我们提供了一种方法,叫做装饰器。使用方法如下:
```js
class Student { //定义类的名称
constructor(name, age, sex, classNum){
this.name = name;
this.age = age;
this.sex = sex;
this.classNum = classNum;
}
study(){
//代码块
};
@expire //哪个方法不使用,或提示一个开始者该方法已过期,就在其上面用@加上自定义的一个标识名
run(){
//代码块
}
}
//装饰器的标识名需要在下方对应一个同名的函数,其包含三个参数。
function expire(target, methodName, descriptor){
//target返回的是类名:Student
//methodName返回的是标记的方法名:run
//descriptor返回的是标记方法的内容
//利用该函数可以编写对应的代码用来改写或提示用户如何调用该方法
}
```
>该方法目前很多浏览器对其支持还不是很好,需要做一些兼容性处理。
### 类的继承
1. 什么样的关系是类的继承?
当有两个类A和B,如果可以描述为B是A,那么就可以说B继承自A(也可以描述为:A派生B,B是A的子类,A是B的父类)。例如:有两个类,一个是交通工具,一个是汽车。我们可以描述为汽车是交通工具,那么就是说汽车的类继承自交通工具类。
当某一个类继承自另一个类时,继承自哪个类,那么它就具有该类所具有的所有特性(包括属性和方法)
如果想让一个类(构造函数)的原型可以继承另一个类的原型,可以使用Object.setPrototypeOf()方法来设置。
2. ES6中继承类
从ES6开始,提供了新的类的继承方法,extends方法和super()方法:
```js
class Animal {
constructor(type, name, age, sex){
//定义了Animal的属性,有种类,名称,年龄和性别
this.type = type,
this.name = name,
this.age = age,
this.sex = sex
}
print(){
console.log(type);
console.log(name);
console.log(age);
console.log(sex);
}
}
//新定义一个狗的类想继承动物类,在定义时加上extends 继承的类名
class Dog extends Animal{
constructor(
super('犬类', name, age, sex);
//super的第一种用法可以当做函数,其用途是表示父类构造函数。因为Dog类继承自Animal类,所以上面的代码即表示Dog类的构造函数继承自Animal的构造函数且type属性固定为'犬类'。
this.loves = '吃骨头'
)
//Dog类继承自Animal类,如果Dog类中也有一个print方法,除了打印Animal方法中的内容外还需要加上Dog方法特有的内容。
print(){
super.print()
//super的第二种用法,用来表示父类。可以在子类中调用父类的方法。这样就可以不用再重复书写父类方法的情况下,调用父类的方法并运行自己特有的代码。
console.log(this.loves)
}
}
//这样就完成了类的继承
```
>注意:ES6要求,如果定义了constructor,并且该类是子类,则必须在constructor的第一行手动调用父类的构造函数。
>如果子类不写constructor,则会有默认的构造器,该构造器需要的参数与父类一致,并且自动调用父类的构造器。
>一个子类只可以继承自一个父类,使用extends方法后。子类会自动继承父类的原型
>子类与父类中存在同名的方法时,子类的实例会优先调用子类的方法。