美文网首页
ECMAScript

ECMAScript

作者: 甄子健 | 来源:发表于2020-11-16 22:04 被阅读0次

ECMAScript它也是一门脚本语言,一般缩写为ES,一般被看作JavaScript的标准化规范,但实际上,JavaScript是ECMAScript的扩展语言,因为ECMAScript只是提供了基本的语法(就是约定了我们代码该如何编写)只是停留在语言层面,并不能直接开发功能。而JavaScript是在ECMAScript上的一种扩展,正是因为这种扩展,让我们可以在浏览器环境中操作DOM和BOM,在node环境中操作读写文件,总的来说,在浏览器中的ECMAScript就相当于JavaScript加上web提供的API(DOM和BOM),而在node环境中ECMAScript就相当于JavaScript加上node所提供的API或内置模块提供的API,所以JS的本身值得就是ECMAScript,从2015年开始ECMAScript就保持每年一个大版本的迭代,很多新特性陆续出现,使JS变得越来越高级。


ES.png

而ES2015更是在上一个版本经历6年才被完全标准化,而这6年正是WEB发展的黄金时期,这个版本中有着许多颠覆的新功能,而正是ES2015发布的内容过多,而导致在接下来的版本,发布更加频繁,更符合互联网快节奏的精神。而在ES2015之后,就以年份命名版本(命名规则发生变化),但是仍有很多人把ES2015称为ES6。下面我们就从ES2015中看看哪些最为核心最为有用。

ES2015(ES6): 新时代ECMAScript标准的代表版本,ES2015标准化文件长达26章
http://www.ecma-international.org/ecma-262/6.0/
下面重点介绍在ES5.1基础上变化后最为重要,最值得了解的新特性,简单归位四大类

  1. 解决原有语法上的一些问题或者不足
  2. 对原有语法进行增强
  3. 全新的对象、全新的方法、全新的功能
  4. 全新的数据类型和数据结构

环境准备

任何一个支持ES2015的环境都可以,可以选用Node.js环境去做具体的尝试,也可以使用最新的Chrome浏览器运行,我们这里用Node.js进行,我们还会用到一个叫Nodemon的小工具,这个工具的作用就是在我们修改为自动执行代码,可在全局或者项目工程中进行安装

yarn nodemon --dev

ES2015 let与块级作用域

作用域顾名思义指的是代码中的某个成员能够起作用的范围,在ES2015之前,只有两种作用域“全局作用域”和“函数作用域”,在ES2015中又增加了块级作用域,块指的是m代码中用花括号({ })包裹起来的范围,在之前没有块级作用域之前,花括号外也可以访问花括号内,在复杂代码中,这是非常不安全的,如下代码

if(true){
    var foo = 'zzz'
}
console.log(foo)//zzz

而在ES2015之后,有了块级作用域我们就可以用新的关键词let去声明变量,它的用法和传统的var一样,只不过let声明的变量只能在所声明的代码块中被访问到

if(true){
    let foo = 'zzz'
}
console.log(foo)//ReferenceError: foo is not defined

这样的特性非常适用于声明for循环当中的计数器传统的循环如果出现循环嵌套的情况,就必须为计数器设置不同的名字,否则就会报错

for(var i = 1; i < 3; i++){
    for(var i = 1; i < 3; i++){
        console.log(i)//0 1 2
    }
}

以上代码是一个很普通的3*3循环嵌套,理论上应该打印9次,却只打印3次。分析原因是因为外层声明i过后内层再次声明i,都是使用var声明,并不是块级作用域内的成员,而是全局成员,内层声明的i就会覆盖掉之前外层声明的i,所以当内层循环结束,外层只会拿到一个值为3的i,就不会满足循环条件,如果使用let就不会有这样的问题,因为let只会在当前循环的代码块中生效。
但是建议不要使用同名的计数器,不利于后期理解代码
还有一个典型的应用场景,就是循环去注册事件时,在事件的处理函数中,我们要去访问循环的计数器,这种情况下以前就会出现问题

//函数模拟
var elements = [{},{},{}] //空对象,代表界面的元素
for(var i = 0; i < elements.length; i++){ //遍历整个数组,模拟为每一个元素添加onclick事件
    elements[i].onclick = function(){
        console.log(i)   //访问当前循环的计数器
    }
}
elements[0].onclick()//在循环结束后,任意调用一个成员的onclick

你会发现最后的值都是3,这是因为我们打印的i始终都是全局作用域的i,在循环结束后i已经被累加到3,所以无论哪个元素都是3(也可以使用闭包解决这类问题,利用函数摆脱全局作用域产生的影响),而现在只需要将var改为let就可以解决(这其实也是闭包机制)

另外在for循环中还有一个特别之处,因为在for循环内部实际上会有两层作用域

for(let i = 1; i < 3; i++){
    let i = 'foo'
    console.log(i)//foo foo foo
}

在上面代码有两个i,可能会产生冲突,但实际上会正常输出,所以表明我们这两个i是互不影响的,不会在同一个作用域中
let和var还有一个很大的区别就是let声明不会出现提升的情况,传统的var声明变量都会导致它会提升到最开始的位置

console.log(foo)
var foo = 'zzz'

我们在声明之前去打印foo,结果并不会出现未定义的错误,这就表明,我们打印的时候foo就被声明了,只是还没有赋值而已,这种现象被叫做变量声明的提升。ES2015中就纠正了这一特性,必须先声明变量再使用变量

ECMAScript const(恒量/常量)

特点就是let基础上多了“只读”这一特性,但正是因为这一特性,声明一个const就必须有初始值

//正确写法
const name = 'zzz'
//错误写法
const name
name = 'zzz'

const所声明的成员不能被修改,只是说不允许在声明过后重新指向新的内存地址,并不是不允许修改恒量中的属性成员

const obj = {}
obj.name = 'zzz' //修改内存空间数据是被允许的

//如果将obj指向一个新的对象就会报错
obj = {}

ECMAScript2015数组的解构

ES2015新增了从数组对象中获取指定元素的一种快捷方式,这是一种新的语法,叫解构

const arr = [100, 200, 300]
//如果我仅想获取300
const [ , , baz] = arr
console.log(baz)//快速提取数组中的指定成员

//还有一种用法
const [foo, ...rest] = arr //...表示从当前位置开始的所有成员,这种...只能在解构的最后一个位置上使用
console.log(rest)//提取的内容会生成数组保存
//200 300

//如果解构位置的成员大于数组的长度,提取到的就是undefined,这就像访问数组中一个不存在的下标
const [foo, bar, baz, more] = arr
console.log(more)

我们也可以用解构给数组加默认值

const [foo, bar, baz = 123, more = 'default value'] = arr
console.log(baz, more)

巧用解构可以给我们的开发带来许多便捷,例如我们要拆分一个字符串,获取拆分后的指定位置,传统做法我们需要用到临时变量,做一个中间的过度

const path = '/foo/bar/baz'
const tmp = path.split('/')
const rootdir = tmp[1]

如果利用到了解构,我们就可以大大简化这个过程

const [, rootdir] = path.split('/')
console.log(rootdir)

对象的解构

除了数组可以被解构,对象也可以被解构,不过对象的解构需要根据属性名去匹配提取而不是位置,因为数组的元素有下标有顺序规则的,而对象里的成员他没有一个固定的次序,所以不能按照位置提取

const obj = {name: 'zzz', age: 18}

//解构
const { name } = obj//提取name属性的值,放在name变量当中
console.log(name)

对象的解构和数组是完全一致的,例如没有的匹配到的成员也会返回undefined,它也可以设置默认值。
还有一个特殊的情况,因为解构的变量名同时又是匹配被解构对象的属性名的,所以说当前作用域中,有同名的成员就会产生冲突

const obj = {name: 'zzz', age: 18}
const name = 'tom'
const { name } = obj
console.log(name)

具体解决方案就是使用重命名来替换

const obj = {name: 'zzz', age: 18}
const name = 'tom'
const { name: objName } = obj
console.log(objName)

对象的解构应用场景也很多,最常见的就是用来简化代码,例如大量用到了console对象里的log方法,我们就可以先把这个方法单独解构出来,然后再去使用独立的log方法

const { log } = console
log('foo')
log('bar')
log('123')

模板字符串字面量

ES2015中还增强了定义字符串的方式,传统定义字符串我们需要通过单引号或双引号,在ES2015中新增了模板字符串的使用方式,它需要使用反引号 (`)标识,

const str = `hello world, this is a \`string\``
console.log(str)

相比传统的方式,这种多了许多有用的新特性,第一点就是传统的字符串并不支持换行,如果有换行符,我们需要用"/n"表示,而模板字符串支持多行我们可以直接换行。
其次模板字符串中可以用差值表达式的方式在字符串中嵌入对应的数值,会比字符串拼接方便的多

const name = 'tom'
const msg = `hey, ${name}` //也可以嵌入语句,而语句的返回值会插入

高级用法:带标签的模版字符串

const str = console.log`hello`

我们可以通过嵌入函数表达式的方式来组合模版字符串,但是我们可以发现,这个的结果是一个数组。于是我们进行以下的探究

const name = 'tom'
const gender = true

function myTagFunc (strings){
    console.log(strings)
}

const result = myTagFunc`hey,${name} is a ${gender}.`

我们发现,这个数组就是按照表达式分割过后的静态内容,所以是一个数组。我们将代码写完整

const name = 'tom'
const gender = true

function myTagFunc (strings, name, gender){
    // console.log(strings, name, gender)
    const sex = gender ? 'man' : 'woman'
    return strings[0] + name + strings[1] + sex + strings[2]
}

const result = myTagFunc`hey,${name} is a ${gender}.`
console.log(result)

这个用法的作用,实际上就是对模版字符串进行加工,更适合用户的阅读。

字符串的扩展方法

includes(), startsWith(), endsWith(),它们是一组方法,可以用来更方便的判断字符串当中是否包含指定的内容。
eg:我们定义了一个message字符串,字符串的内容是一个错误信息,我们假设这是程序运行过程中产生的错误消息,
我们想知道这个字符串是否以Error开头,就可以用startsWith的方法判断。
我们想知道这个字符串是否以'.'结尾,就可以用endsWith的方法判断。
如果我们想看字符串中间是否包含这个内容,例如我们想知道这个字符串是否包含‘foo’。我们就可以使用include方法。

const message = 'Error: foo is not defined.'

console.log(
    message.startsWith('Error')
)
console.log(
    message.startsWith('.')
)
console.log(
    message.includes('foo')
)

参数默认值

可在函数中,给形参一个默认值‘function(enabled = true)’,这样的好处就是你在调用时,就算没有传入实参,依旧有一个默认值。
注:如果有多个形参,请将要定义默认值的形参放在最后,不然会影响函数无法工作。

剩余参数

在ES中,很多方法都可以传任意个数的参数,对于未知个数的参数,以前我们都是通过arguments对象去接收,arguments对象实际上是一个伪数组

function foo(){
    console.log(arguments)
}

foo(1, 2, 3, 4)
//[Arguments] { '0': 1, '1': 2, '2': 3, '3': 4 }

而在ES2015中新增了一个‘...’的操作符,这种操作符有两个作用,这里我们要用到剩余操作符

function foo(...args){
    console.log(args)
}
foo(1, 2, 3, 4)
//[ 1, 2, 3, 4 ]

因为接收所有操作符,所以这种只能出现在形参的最后一位,而且只可以使用一次

展开数组

另一个就是展开,这里我们介绍数组展开,例如这里有一个数组,我们想要把数组里的每一个成员按照次序传递给console.log

const arr = ['foo', 'bar', 'baz']
console.log(...arr)

箭头函数

在ES2015中还简化了函数表达式的定义方式‘=>’,这种函数一来简化了定义,二来增加了新的特性,传统我们需要通过function定义,现在我们可以用尖头函数定义一个完全相同的。

const inc = n => n + 1

console.log(inc(100))

箭头的左边就是参数的列表,如果有多个参数,可以使用()定义,可以使用花括号包裹,只不过使用了花括号就要使用reture关键词返回
箭头函数还有一个很重要的变化,就是不会改变this的指向(箭头函数没有this的机制)

Object

ES2015中对Object提供了一些扩展方法,我们来看几个最主要的方法

Object.assign

这个方法可以将多个源对象中的属性复制到一个目标对象中,如果目标对象中有相同的属性,源对象里的属性就会覆盖掉目标对象的属性

const source1 = {
    a: 123,
    b: 123
}

const source2 = {
    a: 456,
    c: 456
}

const result = Object.assign(source1,source2)
console.log(result)
//{ a: 456, b: 123, c: 456 }
Object.is

用来判断两个值是否相等,以前我们判断相等用的是===,但是严格相等也有一个问题,它对数字0的正负是没有办法区分的,其次是对于NaN两个NaN在严格相等下是不相等的,在今天看来NaN就是一个特殊的值,应该相等。所以我们用Object.is方法解决这个问题

console.log(Object.is(+0, -0))//false
console.log(Object.is(NaN, NaN))//true

Proxy

用来监视对象的读写,专门为对象设置访问代理器。

const person = {
    name: 'zzz',
    age: 20
}

const personProxy = new Proxy(person, {
    get (target, property) {
        return property in target ? target[property] : 'default'
    },
    set (target, property, vaule) {
        if(property === 'age')
        {
            if(!Namber.isInteger(vaule)){
                throw new TypeError(`${vaule} is not an int`)
            }
        }
        target[property] = vaule
    }
})

Proxy对比defineProperty

defineProperty只能监视对象属性的读写,Proxy能够监视到更多对象操作


image.png

Proxy还能更好的支持数组对象,最常见就是通过重写数组的操作方法,下面我们来看,如何用Proxy对象对数组进行监视

const list = []

const listProxy = new Proxy(list, {
    set (target, property, vaule){
        console.log('set', property, vaule)
        target[property] = vaule
        return true
    }
})
listProxy.push(100)
//set 0 100
//set length 1

Proxy是以非侵入的方式监管来对象的读写,一个已经定义好的对象,我们不需要对对象本身做任何操作就可以监视到读写。

Reflect

是ES2015中提供的全新的内置对象,它属于一个静态类,不能使用new方法构建实例对象,它的内部封装了一系列对对象的底层操作(14个静态方法),其实Reflect成员方法就是Proxy处理对象的默认实现。

const obj = {
    foo: '123',
    bar: '456'
}

const proxy = new Proxy(obj, {
    get (target, property){
        console.log('watch logic~')
        return Reflect.get(target, property)
    }
})

console.log(proxy.foo)

它最大的意义就是提供了统一的一套API来操作对象

Class

在此之前,都是通过定义函数或者定义函数的原型对象来实现类型

class Person {
   constructor (name){
       this.name = name
   } 
   say(){
       console.log(`hi, my name is ${this.name}`)
   }
}

const p = new Person('tom')
p.say()

类的继承

在ES2015之前,我们会使用原型的方式,实现继承。现在有了一个专门的关键词extends

Set数据结构

Map数据结构

Symbol

在ES2015之前对象的属性名都是字符串,就有可能重复,就会产生冲突。现如今在我们的开发中,会扩展许许多多的模块,你是不知道这个指令的键,就会产生冲突。所以引入symbol,而ES2015后就可以用symbol类型当做属性名,symbol都是独一无二的,所以就不会产生冲突。

for...of循环

ES2015引入了全新的遍历方式for...of循环,这种循环以后会作为遍历所有数据结构的统一方式

const arr = [100, 200, 300, 400]

for(const item of arr){
    console.log(item)
}

这个循环可以用break关键词随时终止循环。for each无法终止。

可迭代接口

ES2015提供了Iterable接口

相关文章

网友评论

      本文标题:ECMAScript

      本文链接:https://www.haomeiwen.com/subject/xdzemktx.html