一、ES6基础
1. ECMAScript 6 简介
ECMAScript(ES6) 是JavaScript语言的下一代标准,已经在2015年6月正式发布了;在 JavaScript 的基础上做了重大的更新,提供了更优雅的语法和特性。
ECMAScript 和 JavaScript 的关系
ECMAScript 和 JavaScript 的关系是,前者是后者的规格,后者是前者的一种实现。日常场合,这两个词是可以互换的。那为什么不叫 JavaScript 为换名字了呢?原因如下:
- 商标,1996年11月,JavaScript的创造者Netscape公司,决定将JavaScript提交给国际标准化组织ECMA,希望这种语言能够成为国际标准。根据授权协议,只有Netscape公司(Javascript 的创造者)可以合法地使用JavaScript这个名字,且 JavaScript 本身也已经被 Netscape 公司注册为商标。
- 想体现这门语言的制定者是 ECMA,不是 Netscape,这样有利于保证这门语言的开放性和中立性。
2. ES6 新特性
let 和 const
ES6提供了两种新的声明变量的方式:let 和 const。其中let 和 const 相比于 var 声明的变量有块作用域,let 和 const 只在于当前的块作用域中有效。而 var 声明的变量是在函数作用域内有效。
没有块级作用域,会出现很多不合理的情况。
1、内层变量可能会覆盖外层变量。例如:
var n = 5;
function f1() {
console.log(n);
if (true) {
var n = 10;
}
}
f1(); //undefined
发生了变量提升,导致内层的n变量覆盖了外层的n变量。 而块级作用域不存在变量提升 ,let所声明的变量一定要在声明后使用,否则就会报错。上述代码如果使用let进行声明,则会报错。
let n = 5;
function f1() {
console.log(n);
if (true) {
let n = 10;
}
}
f1(); //报错
ES6 明确规定,如果区块中存在let和const命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。总之,在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”。这样可以减少运行时的错误,防止在变量声明前就使用这个变量,从而导致意料之外的行为。
2、用来计数的循环变量会泄露为全局变量。例如下面的代码如果使用var,最后输出的是10。
var a = [];
for (var i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 10
其中变量i是var命令声明的,在全局范围内都有效,所以全局只有一个变量i。所有数组a的成员里面的i,指向的都是同一个i,导致运行时输出的是最后一轮的i的值,也就是 10。
如果使用let,声明的变量仅在块级作用域内有效,最后输出的是 6。
var a = [];
for (let i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 6
变量i是let声明的,当前的i只在本轮循环有效,所以每一次循环的i其实都是一个新的变量,所以最后输出的是6。JavaScript 引擎内部会记住上一轮循环的值,初始化本轮的变量i时,就在上一轮循环的基础上进行计算。
变量的解构赋值
按照一定的模式,从数组或对象中提取值,对变量进行赋值的操作。
数组的解构
var [a,b,c] = [1,2,3] // 结果 a=1 b=2 c=3
var [a,[b,c]] = [1,[2,3]] // 结果 a=1 b=2 c=3
//解构不成功
var [a] = []; // a = undefined
var [a, b] = [1]; //a=1 b=undefined
//不完全解构
var [a] = [1,2]; //a=1
//默认值
var [a=1] = [] //a=1
如果等号的右边不是数组(或者严格地说,不是可遍历的结构),那么将会报错
let [a] = 1;
let [a] = false;
let [a] = NaN;
let [a] = undefined;
let [a] = null;
let [a] = {};
对象的解构
var { b, a } = { a: "aaa", b: "bbb" };
a // "aaa"
b // "bbb"
字符串拓展
模版字符串
模板字符串(template string)是增强版的字符串,用反引号(`)标识。它可以当作普通字符串使用,也可以用来定义多行字符串(类似 pre 标签的作用),或者在字符串中嵌入变量。
// 普通字符串 想要换行需要加上 '\n'
`In JavaScript '\n' is a line-feed.`
// 多行字符串
`In JavaScript this is
not legal.`
// 字符串中嵌入变量
var name = "ac", time = "today";
`Hello ${name}, how are you ${time}?` // Hello ac, how are you today?
rest 展开运算符
function example(...values){
console.log(values)// console: [1,2,3,4]
}
example(1,2,3,4)
var a = [1,2,3]
var b = [...a,4,5,6] //b = [1,2,3,4,5,6]
箭头函数
ES6 提供了新的方式 => 来定义函数
var func = parm => parm
等同于
var func = function (parm){
return parm
}
如果函数没有参数或有多个参数,那么:
var func = () => //some code
等同于
var func = function (){
some code
}
var func = (parm1,parm2) => //some code
等同于
var func = function (parm1,parm2){
some code
}
如果箭头函数的函数体只包含一行代码,则可以不需要写大括号以及 return 语句返回(如果有返回值)
var sum = (num1,num2) => num1+num2
等同于
var sum = (num1,num2) => {return num1+num2}
等同于
var sum = function(num1,num2){return num1+num2}
箭头函数使得表达更加简洁
[1,2,3].map( item=> 2 * item)
等同于
[1,2,3].map(function(item){
return item * 2
})
[1,3,2].sort((a,b) => a - b)
二、面向对象基础
什么是面向对象编程
- 把一组数据结构和处理它们的方法组成对象(Object);
- 把相同行为的对象归纳为类(Class), 通过类的封装(encapsulation)隐藏内部细节;
- 通过继承(inheritance)实现类的特化(specializetion)/泛化(generalization),;
- 通过多态(polymorphism)实现基于对象类型的动态分派。
面向对象的好处?
- 接近人的思维,符合人类对现实世界的认知;
- 封装特性可以使开发者不必在意内部的具体实现,更方便互相协作;
- 继承特性可以减少代码冗余,实现代码复用;
- 多态特性令子类相比父类有不同的行为,这是非常接近现实的;
封装
1、字面量模式
var Cat = {
name : '',
color : ''
}
这样的方法的缺点是如果多生成几个实例,写起来就非常麻烦。
2、工厂模式
function Cat(name,color) {
return {
name:name,
color:color
}
}
这样写可以解决生成多个实例的问题,比较方便,但是调用之后实例的类型只是一个普通的对象,而不是一个具体的类型。
3、构造函数模式
function Cat(name,color){
this.name=name;
this.color=color;
this.eat = function(){};
}
var cat1 = new Cat("big","yellow");
var cat2 = new Cat("small","black");
这样解决了实例不为一个特定类型的问题,使用instanceof运算符可以验证原型对象与实例对象之间的关系。
console.log(cat1 instanceof Cat); //true
但是这样有一个问题是:每一次生成一个实例,都会产生一个新的内容一模一样的方法,是重复的部分,会多占用内存空间。
4、原型模式
function Cat(){ }
Cat.prototype = {
type='animal';
this.eat = function(){};
}
使用原型模式所有实例的type属性和eat()方法,其实都是同一个内存地址,指向prototype对象,因此就提高了运行效率。但是对任何一个实例的操作会影响到所有的实例。
5、混合模式
function Cat(name,color){
this.name = name;
this.color = color;
}
Cat.prototype.type = "animal";
Cat.prototype.eat = function(){};
需要变化的属性使用构造函数定义,不变的属性和方法使用原型来定义。但是这种写法不够美观。
6、类
ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。新的class写法可以让对象原型的写法更加清晰、更像面向对象编程的语法。
class Cat(){
constructor(name,color) {
this.name = name;
this.color = color;
}
eat() {}
}
继承
Class 可以通过extends关键字实现继承,例如定义了一个ColorPoint类,该类通过extends关键字,继承了Point类的所有属性和方法。
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}
class ColorPoint extends Point {
}
super关键字表示父类的构造函数,用来新建父类的this对象。子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类自己的this对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用super方法,子类就得不到this对象。
class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y); // 调用父类的constructor(x, y)
this.color = color;
}
toString() {
return this.color + ' ' + super.toString(); // 调用父类的toString()
}
}