强力学习 ES6 新语法

327 阅读16分钟



一、变量的声明 var、let、const

ES6 之前只有var的变量声明,ES6之后新增了 let、和 const 相对于varlet对我个人而言 是比较好用的。

let 不用担心全局命名空间的污染,let的特性如下:

1、let 命名的变量不会被提升

console.log(a)
let a = 10
//a is not defined

2、let 不能重复命名变量

let a = 10
let a = '小明'
console.log(a)
//Identifier 'a' has already been declared

3、let 命名会形成块级作用域

{
    let a= 10
    var b = 20
}
console.log(b)     //20
console.log(a)     //a is not defined

上面代码‘{ }’就是一个代码段 let只在这里面起作用 但是var变量是全局的 所以正常输出。

4、let会造成暂时性死区

在一个代码块中只要有let声明的变量,在这句语句之前这个变量是不允许被使用的,在语法上称之为 暂时性死区。

ES6规定代码块中存在let、const命令,代码块就对这些命令声明的变量产生了封闭作用,不受外界影响。

var a= 123;

if (true) {
  a= 'abc'; // ReferenceError
  let a;
}

for循环中的 let  与 var

var arr = []
for(let i=0; i<5; i++){
    arr[i] = function(){
        console.log(i)
    }
}
console.log(i)    //i is not defined
arr[3]()          //3

在这里 let 声明的 i 只在for循环的代码段中有效出了for循环就会报错,而且每次循环循环体中的 i 都是新的变量,所以arr[3]() 就会输出3

var arr = []
for(var i=0; i<5; i++){
    arr[i] = function(){
        console.log(i)
    }
}
arr[3]()        //5

这里var声明的 i 在全局有效 当调用arr[3]()的时候会去访问全局唯一的 i 而此时的 i 经过for循环已经变成 5 了 所以这里不管是 arr[3]() 还是 arr[1]() 都是输出 5

总结 let 声明变量只在当前的代码块中有效,每一个 { } 就是一个代码块 let 会在声明的代码块中形成一个 块级的作用域

const 用来声明常量 (一次声明,终生使用--不可被更改)

const a = 10
a = '小明'
console.log(a)  // Assignment to constant variable.

const 声明的对象 不能被重新 赋值  但对象的成员变量 是可以被更改的

const obj = {
    name = 'xiaoming'
}
obj ={}  //Assignment to constant variable.

const obj = {
    name = 'xiaoming'
}
obj.name = '小明'
console.log(obj.name)  // 小明

解释:obj中存放的只是 对象在堆区的地址 这个地址是不能变化的,重新给obj直接赋值的话相当于改变这个地址 设个是不允许的,但是对象内部的成员变量并不是const定义的所以可以进行更改

说明const也可以形成一个块级作用域

二、字符串的扩展

1、模板字符串 格式 `` (称之为 反引号  -- 键盘Tab键上方的键)

1、保留了定义普通字符串的效果

var str = `I am xiaoming`

2、定义多行字符串 会把字符格式一起打印出来 比如换行

var str = `I
am 
xiaoming`
console.log(str)
/*I
am 
xiaoming*/

3、带参数的字符串 参数需要用 ${/*参数*/} 的形式

var name = '小明'
var str = `I am ${name}`
console.log(str)    //I am 小明

4、有简单的表达式

var age = 17
var str = `I will ${++age} years old`    //I will 18 years old    

5、拼接html标签

document.body.innerHTML = `<em>this is em </em>`



2、padStart 和 padEnd 字符串长度不足时 在头部或尾部以某种格式字符补全 方法有两个参数 第一个为 字符串的长度 第二个是 补充的字符

var str = 'abc'
str = str.padStart(8,'*')
console.log(str)        //*****abc

var str = 'abc'
str = str.padEnd(8,'*')
console.log(str)        //abc*****

如果原字符串长度 等于或大于 设定的字符串长度那么将返回 原字符串

var str = 'abc'
str = str.padStart(2,'*')
console.log(str)        //abc 

3、includes 判断字符串是否包含某字符或字符串 返回布尔值 true 或 false

var str = `todayisgoodday`
console.log(str.includes('day'))        //true
console.log(str.includes('o'))            //true

4、startsWith 和 endsWith 判断字符串是否是以 某字符串 开头 或以某字符串结尾 返回true 或 false

var str = `todayisgoodday`
console.log(str.startsWith('day'))         //false
console.log(str.startsWith('to'))          //true
console.log(str.endsWith('day'))           //true
console.log(str.endWith('to'))             //false

5、repeat 返回一个新字符串 该字符串是原字符串重复n次的结果

var str = 'string/'
str = str.repeat(3)        // string/string/string/

如果是小数将自动取整 (只取整数部分,并不是四舍五入)

var str = 'string/'
str = str.repeat(2.3)        //string/string/

var str = 'string/'
str = str.repeat(2.9)        //string/string/

以上代码 不论是2.3还是2.9到最后都只重复两次

6、tirm ,tirmLeft 和 tirmRight 用于去除字符串空格

tirm 去除字符串左右的空格

var str = ' string '
str = str.tirm()        // str = 'string'

tirmLeft 去除字符串左边的空格

var str = ' string '
str = str.tirmLeft()   // str = 'string '

tirmRight 去除字符串右边的空格

var str = ' string '
str = str.tirmRight()   // str = ' string'

三、数组的扩展

1、扩展运算符即    ...  三个点

扩展运算符作用 将数组展开,可以理解为将数组的中括号去掉 暴露出里面的属性值

var arr = [1,2,3]
var arr2 = [arr]        //arr2 = [[1,2,3]]

上面是没有使用扩展运算符的,和容易理解arr2的值,直接将arr作为arr2的第一个元素了

var arr = [1,2,3]
var arr2 = [...arr]    // arr2 = [1,2,3]

这个是使用了扩展运算符,可以这样理解将arr的 [] 的壳给 打开了里面的东西就落入了arr2里面了。

扩展运算符还可以将字符串转换成数组

var str = 'hello'
var strs = [...str]        //strs = ['h','e','l','l','o']

2、Array.from 返回一个数组,将一个伪数组(类数组)的对象转化成为一个真正的数组

//NoteList 对象
var lis = document.getElementsByTagName('li')
Array.from(lis).forEach(item => {
    ......
})

//argumrnts 对象
function(){
    var arr = Array.from(arguments)
    ......
}

只要是类似数组或者可以遍历的对象都可以被 Array.from 转换成真正的数组

上面代码中 lis 对象的结构是


模仿上面的 notelist对象 

var test = {
    0 : 'a',
    1 : 'b',
    2 : 'c',
    length : 3
}
var arr = Array.from(test)
console.log(arr)          // [a,b,c]

3、Array.of  该方法 用于将一组参数 转化成为数组

var arr = Array.of('1','2',3)    // ['1','2','3']
var arr = Array.of(2)            // [2]
var arr = Array.of()             // []

在数组中 数组的构造器方法也可以将一组数组转化成数组

var arr = new Array()        //[]
var arr = new Array(3)       //['','','']
var arr = new Array(2,3,4)   //[2,3,4]

可以看出 利用构造器创建数组时在传入参数个数不同的情况下 产生的 数组会有些不同,当只传入一个数字时,它会把这个数字当成数组的长度来生成数组,但是Array.of 正好弥补了参数个数不同而带来的麻烦,它会将传入的参数毫无差别的当做数组中的元素。

4、findfindIndex 用来查询数组

find 方法 接收一个回调函数 遍历数组中的每一项去检测是否符合回调函数中的规则直到找到第一个符合规则的成员,并将该成员返回,如果一直没有找到,将返回undefined

 var ages = [3, 10, 18, 20, 30];
 function checkAdult(age) {
      return age > 18;
   }
 console.log(ages.find(checkAdult));       // 20

findIndex 方法与 find 方法一样 查询数组 但是findIndex 返回的是数组中第一个符合条件成员的索引

var ages = [3, 10, 18, 20, 30];
function checkAdult(age) {
     return age >= 18;
}
ages.find(checkAdult);                    // 3

5、includes 用来判断数组是否包含某个给定的值,返回truefalse。 与字符串的includes极为相似

var arr= [1,2,3]
var flag = arr.includes(1)    //flag 为true

6、fill 方法,用给定的值来填充数组,如果该数组中有元素,原来的值将会被覆盖掉

 var arr = new Array(5)
 arr.fill("a")            //  arr = ['a','a','a','a','a']

var arr = [1,2,,,]
arr.fill('a')            // arr = ['a','a','a','a','a']

7、indexOf 返回这顶参数在数组中的索引,如果数组中没有改数据则返回 -1

var arr = [1,2,3]
var index = arr.infexOf(1)  // index = 0

var arr = [1,2,3]
var index = arr.indexOf(4)  //index = -1

四、函数的扩展

1、箭头函数 格式 ()=>{}

ES6 新增箭头函数命名函数,极大简化了函数的书写,在ES6 之前每当我们要创建一个匿名函数的时候要用 function去定义,ES6之后我们就可以简单的多了

//ES5
var fn = function(){
    .....
}
//ES6 
var fn = () => {}

箭头函数也是可以添加形参的 ,只有一个参数的时候我们可以把 ()省略掉或是 方法体只有一句话的时候可以把 {} 省略掉,如果这句话是return 返回语句 必须将return省去

//一般情况
var fn = (a,b,c) => {}

//一个参数
var fn = item => {}

//一个参数一句函数体
var fn = item => ++item

例子:将箭头函数 用在forEach中

[1,2,3,4].forEach(item=>item*2)

箭头函数中使用this

首先说明箭头函数是没有this的,因此它自身不能进行new实例化,同时也不能使用call, apply, bind等方法来改变this的指向,所以在箭头函数中使用this 始终指向的是定义它的那个对象,除此之外箭头函数固定了this的指向,在箭头函数中this的指向不可被更改

var obj = {
    move : function(){
        document.addEventListener('click',
        event => this.do(event.type), false)
    }
    do  : function(){
        alert('hello')
    }
}

这里document的监听事件使用来箭头函数所以其中的this指向当前的obj对象,如果不用箭头函数this将指向事件源document

注意:箭头函数中不能使用arguments

2、形参的定义初值

ES5 要给一个参数赋初值 要在函数体中进行判断undefined如下

function(a , b){
    b = b || 'hello'
}

//或是一下写法
function(a ,b){
    if(typeof b = 'undefined') b = 'hello' 
}

ES6新增了可以给函数的参数添加初始值,这样就优化了代码,代码的可读性也强

function(a,b='hello'){
    .....
}

这样很清楚的知道参数 b 是可以为空的

3、函数的length属性

函数的length属性表示函数预期要传入的参数的个数(不包含有初值的参数)

(function(a=1){}).length    // 0
(function(a,b){}).length    // 2
(function(a,b,c=2){}).length  // 2

如果有未赋初值的参数在有初值参数后面那么length将也不把这些参数计入其中

(function(a,b=2,c){}).length    // 1

总结:函数的 length 表示函数参数中第一个有初值的参数之前的参数个数

4、立即调用函数表达式(IIFE)

IIFE 避免里全局变量和函数的污染,正常的写法可能会造成变量和函数的污染造成不必要的的麻烦

var a = 100
var a = 'a'
function f(){
    console.log(a)
}
var f = function(){
    var a = 0
    console.log(a)
}
f()

在这种情况下a变量和f函数都被污染了,如果你写了f函数又引入了一个别人的js正好里面也有个f函数,而两个函数又都定义在全局,那么就像上面的这种情况后面的函数或变量将前面的函数或变量给覆盖了,就会造成程序的错误,这种时候就要用IIFE去解决这种问题

格式一利用():(function(){})() 

(function(){
    var a = 10
    function f(){
        console.log(a)
    }
    f()        // 10
})()

console.log(a)  //a is not defined

格式二利用 单目运算符 : +function(){}()  -function(){}()   !function(){}()

!function(){
    var a = 10
    function f(){
        console.log(a)
    }
    f()        // 10
}()

console.log(a)  //a is not defined

格式三:(function(){}())

(function(){
    var a = 10
    function f(){
        console.log(a)
    }
    f()        // 10
}())
console.log(a)  //a is not defined

 个人比较偏向于第三种,它让代码看起来更紧凑,更像一个整体

IIFE 也可以带参数

毕竟说到底它也还是个函数,要有一般函数的性质

(function(a){
    function(){
        console.log(a)
    }
}(10))

五、对象的扩展

1、对象属性的简化

ES6允许直接在对象中写入变量

var name = 'xiaoming'
var obj = {
    name
}

当对象中只有变量的时候,说明对象的属性名是变量,属性的值是变量的值

2、方法的简化

ES6不仅简化了属性的写法,还简化了对象中方法的写法

//ES5
var obj = {
    add: function(){
        console.log('add')
    }
}

//ES6
var obj = {
    add(){
         console.log('add')
    }
}

3、属性名表达式

使用字面量(大括号)定义对象,有两种方法定义对象的属性

//方法一 (标识符)
obj.name = 'xiaoming'

//方法二 (表达式)
obj['n'+'ame'] = 'xiaoming'

在ES6之后允许使用 表达式给对象的属性赋值,但是ES5只能使用标识符的方法给对象的属性赋值

var str = age
var obj= {
    name : 'xiaoming',
    [str] : 16,
    ['he'+'ight'] : 180cm'
}

console.log(obj.name)        // xiaoming
console.log(obj.str)         // 16
console.log(obj.age)         // 16
console.log(obj.height)      // 180cm

ES6还允许使用运算符的形式个对象的方法命名

var obj = {
    ['a'+'d'+'d'] : function(){
        console.log('this is add function')
    }
}
obj.add()            // this is add function

4、Object.assign

Object.assign 用于对象的合并,将源对象的所有的可枚举属性,复制到目标对象。

该方法接收两个及以上参数,第一个参数是目标对象,assign这个方法把其它的参数中的属性全部加在第一个参数身上,

如果其他参数中有重复的属性,将会用这个属性最后一次出现在的对象中的值。

assign这个方法的返回值就是第一个参数的引用,也就是返回了第一个参数。也就是说是对第一个参数的修改。

var obj1 = {}
var obj2 = {
    name : 'xiaoming'
}
var obj3 = {
    age = '18'
}

Object.assign(obj1,obj2,obj3)             //obj1 = {name: 'xiaoming',age:'18'}

注意assign还有以下特点

1、 assign不能拷贝继承过来的属性

2、assign也不拷贝不可枚举的属性

var obj1 = {}
var obj2 = {
    name : 'xiaoming',
    age  : 18
}
obj2.defineProperty(obj2,'sex',{
    value: 'man',
    configurable : true,
    writable : true,
    enumerable : false
})
Object.assign(obj1,obj2)        // obj1 = {name:'xiaoming',age:18}

此时obj1中并不会出现sex属性,defineProperty 方法使用来精细化设置属性的,这里试将sex属性设置为了不可枚举的,所以assign没有拷贝到sex属性

浅拷贝

assign实行的是浅拷贝也就是说如果对象的属性是引用数据类型的时候,拷贝过去的只是对象或数组的指向(可以理解为地址)

var obj1 = {}
var obj2 = {
    name : 'xiaoqiang',
    friends : ['one','two']
}
Object.assign(obj1,obj2)
obj2.friends.push('three')
console.log(obj1)            //{name: 'xiaoqiang',friends:['one','two','three']}
console.log(obj2)            //{name: 'xiaoqiang',friends:['one','two','three']}

当你改变其中一个对象中的值的时候两个对象中的会都改变了,因为两者属性保存的是同一个内存地址,也就是说指向同一块内存空间,一个改变了这个空间的值另一个当然也会改变

5、属性的遍历

ES6现在有五种可用来遍历对象的方法

1、for...in  遍历对象本身以及原型链上面可枚举的属性

2、Object.keys(obj) : 返回一个数组包含对象自身所有可枚举的属性键(不包括继承过来的)

3、Object.getOwnPropertyNames(obj): 返回一个数组包含对象自身全部的属性的键名(包括不可枚举的属性)

4、Object.getOwnPropertySymbols返回一个数组,包含对象自身的所有 Symbol 属性的键名。

5、Reflect.ownKeys返回一个数组,包含对象自身的所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举。

6、继承相关的方法

1、Object.create()

使用该方法比较适合于对字面量对象的继承

var obj = {
    name : 'xiaoming'
}
var obj2 = Object.create(obj)
console.log(obj2.__proto__ == obj)            //true

以上代码中的 obj2.__proto__(前后各两个下划线)指向的是obj2 的原型,它与obj相等说明obj2继承于obj

2、getPrototypeOf

getPrtotypeOf 用来获取创建该对象的构造器的原型(prototype)属性。

var obj = {
    name : 'xiaoming'
}
console.log(Object.getPrototypeOf(obj) == Object.prototype)        // true

3、防篡改方法

防篡改方法对对象起到一定的保护作用,一共分为三个等级:

1、preventExtensions() 不允许新增,但是可以修改,也可以删除

var obj = {
    name : 'xiaoming'
}
Object.preventExtensions(obj)
obj.age = 12
obj.name = '小明'
console,log(obj)            // {name:'小明'}

2、seal: 不允许新增,也不允许删除,但是可以修改如下

var obj = {
    name : 'xiaoming',
    age : 16
}
Object.seal(obj)
delete obj.age
obj.name = '小明'
console,log(obj)            // {name:'小明',age:16}

3、freeze: 不允许新增,也不允许删除和修改

var obj = {
    name : 'xiaoming',
    age : 16
}
Object.seal(obj)
delete obj.age
obj.name = '小明'
obj.sex = 'man'
console,log(obj)            // {name:'xiaoming',age:16}

六、class 以及 extends 语法

ES6允许在js中使用class关键字去声明一个类,也可以使用extends去实现类之间的继承,当然要有super语法了

基本格式:

class 类名 {
    constructor {
       this.属性 = 参数
    }
    方法名(){}
}

需要注意几个点

1、类名定义采用 大驼峰命名法(每个单词的首字母大写),类名后面紧跟 { } 不用加 ()

2、在类的 { } 中不能直接写语句(不象构造器),方法语句要写在方法中,属性定义在 constructor 里面

3、方法与方法之间没有逗号

使用extends实现继承

基本格式:

class 子类 extends 父类 {
    constructor() {
        super()
        this.属性 = 参数
    }
    方法名(){}
}

注意:必须在 constructor 中使用super方法 才能把父类的属性继承过来 ,使用了 extends 之后父类的方法会自动继承

类中的静态方法static

静态方法可以直接通过 类名.方法() 的格式调用 ,js中的静态方法也很简单就是,在class中的方法名前面 加上 static 关键字

class P {
    static say(){}
}
var obj = new P()
P.say()
obj .say()                // say  is not a function

注意:静态方法只能 类名. 的格式调用,new出来的实体对象不能调用静态方法

七、set与map

ES6新增的两种数据结构 set 和 map

1、set 和数组相似,不过set中存放的是唯一的值

通过new构造的方式创建一个 set 对象可以传入一个数组给其赋初值

var set = new Set([1,2,3])

add方法可以给set对象添加数据

set.add(4)
set.add(5)

delete方法删除set中的数据

set.delete(5)

遍历set

遍历set要使用ES6新增的  for...of  ,不能使用forEach,不能使用 fo...in

for(var item of set){
    console.log(item)
}
//1
//2
//3
//4

2、map 类似于对象,map中以键值对的方式存放数据

使用Map构造器创建map对象,可以放入一个类似于二维数组的参数给其赋初值

var map = new Map([
    ['a','hello'],
    [1,'hi']
])

说明:内层数组中 的第一个参数将来会变成map中的键,第二个参数,是 该键名对应的值

通过set方法给Map对象添加属性,set接收两个参数,第一个是键名,第二个是键值

map.set('key','value')

通过get方法获取到Map对象中的属性值,get接收一个参数为map中的键名

map.get('key')            //value

八、严格模式

严格模式字面意思理解就是,对于语法有更加规范的要求。

严格模式和非严格模式有什么区别:

1、在严格模式下不能使用没有var的变量

"use strict"
a = 10
console.log(a)            // a is not defined

2、在严格模式下不能8进制的数字

"use strict"
a = 05            //Octal literals are not allowed in strict mode.

3、在严格模式下不能把函数定义在if语句中

"use strict"
if(true){
    function f(){
        console.log('f')
    }
}
f()            //f is not defined

4、在严格模式下函数不能有重名的形参

"use strict"
function f(a,a){
   console.log('f')
}            //Duplicate parameter name not allowed in this context

5、在严格模式下不能arguments就不能跟踪形参了

"use strict"
function f(a,b){
   console.log(a)            //1
    arguments[0] = 'yi'
    console.log(a)            //1
    console.log(arguments[0])    //yi
}
f(1,2)

6、在严格模式下不能function中的this就再在是window,而是undefined

怎样启动严格模式

在代码快开始 添加 "use strict"  

<script>
"use strict"

</script>

function(){
    "use strict"
}

结尾:

当然在ES6中还有很多新增的语法,比如promise,Symbol, async,Module 等等,要想真正掌握ES6需要是长时间的练习才行,我这里只是简单的总结,学习ES6的新同学,推荐去读阮一峰老师的《ECMAScript6 入门》