模板字面量

2. 模板字面量

  1. es6之前.想实现多行字符串,要么使用一个原来js,语言级bug来实现一个多行.
  2. 把字符串替换已存在的变量值的能力
  3. HMTL转义

2.1 基本语法

使用一个反引号 `` 用来包含普通字符串,而不是之前的双引号或单号

let msg = `hello ES6`;
console.log(msg)

在以上这 个案例中,并没有感受到和之前有什么不一样.

如果想要输出反引号 `` 可以使用 转义符 \

let msg = `\`hello ES6\``;
console.log(msg)

在模板字符串里面.如果想要输出单引号或双引号.就不需要转义符 \

let msg = `"hello"'ES6'`;
console.log(msg)

2.1 多行字符串

在ES6之前想要实现多行字符串.可以使用一个语法BUG,在换行之前使反斜线 ( \ ),可以用于创建多行字符串.为什么不用.因为是一个语言级的BUG.你不知道什么时候他被修复了.

var msg = "hello \
ES6";
console.log(msg)

这里确实换行了.并且可以被输出.但是打印的时候并不会真正的换行.因为反斜线 ( \ )是延续符号,而不是新行的符号.如果你想要在输出中换行.可以这样写

var msg = "hello \n\
ES6";
console.log(msg)

ES6中推出的多行字符串就不需要使用这种BUG了只需要在你需要的地方包含换行就可以了

let msg = `hello 
es6;
`
console.log(msg)

需要注意提所有的空格都会被认为是内容的一部分,所以需要注意空格的问题.保持合理的缩进

let ouput = `

<h1>
    <div>hello</div>
</h1>

`.trim();
console.log(ouput)

trim()可以清空字符串左右两边的空格.

如果只是单纯想要实现输出结果的换行可以使 \n ;

以上只是可以实现换行...但是真正的意见在于允许你使用js表达式嵌入到模板字面量当中.

比如:

let msg = 'hello';
let res = `${msg},world`;

console.log(res)

替换位 \${msg} 替换位是\$开始,与结束{}来界定.之间可以放入任意的JS表达式.以上是最简单的示例

既然替换位是表达式.哪么可替换的就不可能只是变量.还可以放计算,函数调用.

let n = 5,
    m = 20.452342,
    msg = `${n+m}`;

    msg = `$${(n * m).toFixed(2)}`
console.log(msg)

在上面这个案例中.我可使用了计算.并且调用方法.在替换位之前的美元符号可以被正常输出.因为没{}跟随其后.

另外模板字面量本身也是JS表达式,这也就是可以把模板字面量嵌入到另一个模板字面量的内部

比如:

let msg = "张三",
    res = `hello ${
        `他叫${msg}`
    }`;
    console.log(res)

现在我们都已经理解 了字面量不用做任何的连接...就可以直接使用多行字符串以膝上计算机插值功能.不过这个还不是真正牛逼的地方..真正厉害的是模板标签(template tag),能对模板字面量进行转换并返回最终的字符串值.标签在模板起始处被指定. 也就是在第一个` 之前

比如

let msg = tag`hello 张三`;

在上面这个例子,tag就是会被应用到hello 张三,

一个标签tag仅仅只是一个函数,在被调用 时接收需要处理的模板字面量的数据.标签所接收的数据被划分为独立的片段,并且必须组合来创建结果.第一个参 数是一个数组,包含被js解释过的字面量字符串.随后的参数是每个替换位的解释值

function tag(val,...args){
// 返回一个字符串
}

用一个列子来解释一下传递给标签的是什么参数.

比如:

let conut = 10;
let price = 0.25;
let msg = tag`${conut} items cost $${(conut * price).toFixed}.`;

如果我有一个tag()的函数.这个函数会接受到三个参数,第一个是一个数组 arr.会包含以下的元素

  1. 在首个替换位之的空字符串 ""
  2. 首个替换位与第二个替换位之间的字符串 items cost $
  3. 第二个替换位之后的字符串 "."

第二个参数会是10, 也就是 ${conut} 变量的解释值. 会成为 args 数组的第一个元素.

第三个也就是最后一个,会是 ${(conut * price).toFixed()} 的解释值 2.50,并且是args数组的第二个元素

示例如下:

["","items cost $","."]
[10,2.50]

需要注意的是 第一个参数数组的第一个元素是一个空字符串,以确保arr[0],总是在字符串的起始分,

也就是正如 arr[arr.length - 1] 总是是字符串的结尾部分.同时替换的元素数量也总比字符量元少1

就意味着,args.length === arr.length-1的值是 true;

在当前这种情况下,,可以交替使用arr 和 args 数组来创建一个结果字符串;

用arr中首个元素开始。。。后面紧跟着args的首个元素。。以此反复。直到结果字符串被全部创建完毕

function fn(arr,...args){
    let result = "";
    // 直接使用args的元素数量来进行循环
    for(let i=0;i<args.length;i++){
    result += arr[i];
    result += args[i];
}
    result += arr[arr.length - 1]
    return 1;
}

let conut = 10;
let price = 0.25;
let msg = fn`${conut} items cost $${(conut * price).toFixed()}.`;

console.log(msg)

在以上这个例子中定义了 fn 标签,能够执行与模板字面量的默认行为相同的换操作,唯一的需要注意的是在循环中使用args.length,而不是arr.length用来避免args的数组越界,这是冂es6对 arr 和 ...args 一个定义;

...args中包含的值不必是字符串,也可以是计算后的结果.

ES6 块级绑定

1. 块级绑定

基本上在类C语言中,变量的创建总是在它被声明的地方.但javascript并非如此.变量实际的位置是取决于开发着如何声明它..基于这样的一个存在,ES6闪亮登场.提供了一个let,可以让你更容易更直观的控制变量的作用域.

1.1. 所谓的变量提升

如果我们使用var 声明变量.如果在函数内.不管我写在什么地方,都会被视为写在函数顶部.如果不在函数内.则视为全局作用域的顶部.就是所谓的变量提升

function getVal(cond){
    if(cond){
        var color = 'blue';
        return color;
    }else{
        // color 在此处可以被访问 值为 undefined
        return null
    }
}

以上代码在js解释器中会为被修改为

function getVal(cond){
    var color
    if(cond){
        color = 'blue';
        return color;
    }else{
        // color 在此处可以被访问 值为 undefined
        return null
    }
}

为了解决这个BUG..引入了块级作用域

块级声明也就是让所声明的变量在指定的块的作用域外无法被访问.块级作用域(词法作用域)

  1. 在一个函数内部
  2. 在一个代码块内部 {}

块级作用域是类C语言的工作机制.

1.2. let 声明

使用方法和var语法一致,基本上你可以用let来代替var进行变量声明,但是会把变量的作用域限制在当前的代码块当中.如果想要提升,需要手动操作
以下示例:

function resu(val){
    if(val){
        let color = 'blue';
        return color;
    }else{
        // color 在此处  color is not defined
        return color
    }
}

function resu(val){
    let color 
    if(val){
        color = 'blue';
        return color;
    }else{
        // color 在此处可以被访问 值为 undefined
        return color
    }
}

console.log(resu(0))

1.3. 禁止重复声明

如果一个标识符已经被定义,就不能在此代码块中使用同一个标识符进行 let 声明,否则会报错

var number = 0;
let number = 1;   // Uncaught SyntaxError: Identifier 'number' has already been declared

在同一作用域下不能得复声明
但是在嵌套的作用域内使用同名的新变量.就不会所错

var number = 10;
if(true){
    let number = 40;
}

以上案例中.let 并没有报错,因为在if条件语句中创建新变量number.而不是在同一级别再次创建.在if代码块中会屏蔽全局的number变量.从而在局部组止对于后者的访问.

1.4. 常量声明

声明一个常量可以使用const语法进行声明.常量意味声明之后的值不可变.所以所有的常量都要在声明的时候初始化.

// 有效的常量
const MAXNUMBER = 300// 语法错误的常量
const NAME;

以上MAXNUMBER这个常量初始化并且赋值了.因此是正常而有作用的.而NAME常量没有被赋值.会导致语法误法;
const和let声明一样都是块级声明.有声明也不会有提升的情况发生

if(true){
    const NUMBER = 40;
}
// NUMBER 在此处无法被访问

已经被声明过的变量.不可以用常量再次声明

var msg = 0;
let height = 190;

// 以下两者都会报错
const msg = '1';
const height = 170;

常量不可被改变

const height = 170;
height = 180;  //Uncaught TypeError: Assignment to constant variable.

常量确定之后.不可以再次被赋值

如果使用const 声明对象.
const声明是会阻止对于常量绑定与常量自身值的修改,也就是说并不会去阻止对于常量的成员的修改;

const obj = {
    name:"张三"
}  //
obj.name = '李四';
// 报错  Uncaught TypeError: Assignment to constant variable.
obj = {
    name:"赵五"
}
// 数组也是一样的
const arr = [];
arr.push(1)
arr.push(1)
console.log(arr)

此处对象初始化的时候有一个属性,修改属性是可以,但是不能修改对象本身.只需要记住const阻止的是对于常量绑定的修改.而不是对于成员值的修改

1.4. 暂时性死区

使用let , const 声明的变量,在达到声明之前.都是无法访问的.就算是相对安全的操作(比如typeof),也是一样的

console.log(typeof abc)
let abc;

以上abc 变量使用了let 进行定义,但是这条语句.永远不会被执行.因为在声明之前就已经报错了.出现这个问题是因为暂时性死区.但是暂时性死区是 js 社区(比如网上论坛)提出的,这个名称从来没有在ECMAscript规范中被明确命名.但是经常用于描述let或const声明 的变量为何在声明处之前无法被访问

1.5. 循环中的块级绑定

我们最需要使用的变量的块级场景是,也许是for,也就是想让一次性的循环计数只能在内部使用
以下示例应该是最常见的一种行为

for (var index = 0; index < 10; index++) {}
console.log(index)   //10

但是在大多数语言中.以上index.只能在循环内工作访问.这里的index在外部可以被访问.是因为使用var声明导致了变量提升,如果换成let,就不会发生

for (let index = 0; index < 10; index++) {}
console.log(index)   //app.js:8 Uncaught ReferenceError: index is not defined

在以上案例中.index仅在for内部使用. 循环结束..这个变量的任意位置不可被访问

1.6. 循环内的函数

在let没有出现之前,因为var的特点.会使循环变量作用域之外还可以被访问,于是在循环内创建函数会有问题.

var arr = [];
for(var i=0;i<10;i++){
    arr.push(function(){
        console.log(i)
    })
}

arr.forEach(function(fn){
    fn()
})

我们本身的预期是希望输出0-9的数字,但是实际结果是数字10被输出了10次.

我们在修正这个问题的时候可以使用IIFE(立即调用函数表达式),就可以在每次迭代中强制性的创建变量的一个新的副本.

var arr = [];
for(var i=0;i<10;i++){
    arr.push((function(value){
        return function(){
            console.log(value)
        }
    })(i))
}

arr.forEach(function(fn){
    fn()
})

上面在循环内使用IIFE,变量 i 被传递给了IIFE, 去创建了value变量为自身的副本,并在自己当中

1.7. 循环内的let

使用let声明.可以简化IIFE.在每交迭代中.都创建一个新的同名变量,对其进行初始化.

var arr = [];
for(let i=0;i<10;i++){
    arr.push(function(){
        console.log(i)
    })
}

arr.forEach(function(fn){
    fn()
})

以上代码完全实现了相同的效果.但是更加简洁.效果更好,同样的方式在for-in 和 for-of中同样适用.

var arr = [];
var obj = {
    name:"张三",
    sex:"",
    age:22
}

for (let key in obj){
    arr.push(function(){
        console.log(key)
    })
}

// console.log(arr)

arr.forEach(function(fn){
    fn()
})

在以上for-in循环中体现出for相同的行为.

let声明在循环内部的行为是在规范中特别被定义过的.而与不提升变量的特征没有必然的联系.事实上在let刚刚出来的时候是没有这种行为的...在后面不停变更过程中加进来的.

1.8. 循环内的常量

ES6规范没有明确禁止在循环中使用常量.然而它会根据循环方式的不同有不同的行为体现.在常规的for当中.可以使用const初始化.但是如果你去循环了.就等于是想要改变它.就会报错

for (const index = 0; index < 10; index++) {
    console.log(index)
}

代码会执行一次.但是第二循环的时候,试图改变index.这个时候就报错了.

但是如果是在for-in或是for-of.效果和let完全相同.

var arr = [];
var obj = {
    name:"张三",
    sex:"",
    age:22
}

//不会报错
for (const key in obj){
    arr.push(function(){
        console.log(key)
    })
}

arr.forEach(function(fn){
    fn()
})

因为每次迭代创建一个新的变量绑定.而不是试图去修改已经绑定的变量的值.所以就不会报错

1.9. 全局的块级绑定

let和const不同于var的另一个区别就是在全局作用域上的表现.在全局作用域上使用var,会创建一个新的全局变量.并且成为全局对象(window)的一个属性.这也就是竟味使用var可能会无意中覆盖一个已经有的全局属性,

var RegExp = 'h';
console.log(window.RegExp)  //h

RegExp定义在window.但还是被重写了.但是如果使用了let 或 const,虽然在全局中会创建新的绑定,但是不会有任何属性被添加到了全局对象上去.也就意味着不用使用let或const来覆盖一个全局变量.只能把他屏蔽.

let RegExp = 'h';
console.log(RegExp// h
console.log(window.RegExp//ƒ RegExp() { [native code] }
console.log(window.RegExp === RegExp)   //false

如果希望代码能从全局对象中访问.依然需要使用var.在浏览器跨窗口去访问代码时适用.

在拥有了let和const之后..默认情况下应该使用let而不是var 对于90%以上开发者来说let的行为才是正确的行为.是声明变量本来就应该有形式.所以直接使用let更合逻辑.在需要被保护的变量时使用const.

1.10. 总结

  1. let或const有块级作用域
  2. var 只有全局和函数作用域
  3. let 不能重复声明,const也不能重复声明
  4. 如果使let 或 const 必须在使用声明.如果在使用后声明,会产生暂时性死区
  5. 可以解决循环中的闭包的问题
  6. var在全局下声明的变量,会挂载到window下面
  7. let或const会挂载到window下面.

ES6概述

ES6概述

ES6 简介

ECMAScript 6.0(以下简称 ES6)是 JavaScript 语言的下一代标准,已经在 2015 年 6 月正式发布了。它的目标,是使得 JavaScript 语言可以用来编写复杂的大型应用程序,成为企业级开发语言。

ECMAScript 和 JavaScript 的关系

要讲清 ECMAScript 和 JavaScript 之间的关系,就需要回顾历史。1996 年 11 月,JavaScript 的创造者 Netscape 公司,决定将 JavaScript 提交给标准化组织 ECMA,希望这种语言能够成为国际标准。次年,ECMA 发布 262 号标准文件(ECMA-262)的第一版,规定了浏览器脚本语言的标准,并将这种语言称为 ECMAScript,这个版本就是 1.0 版。

该标准从一开始就是针对 JavaScript 语言制定的,但是之所以不叫 JavaScript,有两个原因。一是商标,Java 是 Sun 公司的商标,根据授权协议,只有 Netscape 公司可以合法地使用 JavaScript 这个名字,且 JavaScript 本身也已经被 Netscape 公司注册为商标。二是想体现这门语言的制定者是 ECMA,不是 Netscape,这样有利于保证这门语言的开放性和中立性。

因此,ECMAScript 和 JavaScript 的关系是,前者是后者的规格,后者是前者的一种实现(另外的 ECMAScript 方言还有 JScript 和 ActionScript)。日常场合,这两个词是可以互换的。

ES6 与 ECMAScript 2015 之间的关系

ECMAScript 2015(简称 ES2015),在2011年,发布了ECMAScript 5.1 版本,之后就开始制定 6.0 版了。因此,ES6 这个词的原意,就是指 JavaScript 语言的下一个版本。

但是,因为这个版本引入的语法功能太多,而且制定过程当中,还有很多组织和个人不断提交新功能。事情很快就变得清楚了,不可能在一个版本里面包括所有将要引入的功能。常规的做法是先发布 6.0 版,过一段时间再发 6.1 版,然后是 6.2 版、6.3 版等等。

但是,标准的制定者不想这样做。他们想让标准的升级成为常规流程:任何人在任何时候,都可以向标准委员会提交新语法的提案,然后标准委员会每个月开一次会,评估这些提案是否可以接受,需要哪些改进。如果经过多次会议以后,一个提案足够成熟了,就可以正式进入标准了。这就是说,标准的版本升级成为了一个不断滚动的流程,每个月都会有变动。

标准委员会最终决定,标准在每年的 6月份正式发布一次,作为当年的正式版本。接下来的时间,就在这个版本的基础上做改动,直到下一年的 6 月份,草案就自然变成了新一年的版本。这样一来,就不需要以前的版本号了,只要用年份标记就可以了。

ES6 的第一个版本,就这样在 2015 年 6 月发布了,正式名称就是《ECMAScript 2015 标准》(简称 ES2015)。2016 年 6 月,小幅修订的《ECMAScript 2016 标准》(简称 ES2016)如期发布,这个版本可以看作是 ES6.1 版,因为两者的差异非常小(只新增了数组实例的includes方法和指数运算符),基本上是同一个标准。根据计划,2017 年 6 月发布 ES2017 标准。

因此,ES6 既是一个历史名词,也是一个泛指,含义是 5.1 版以后的 JavaScript 的下一代标准,涵盖了 ES2015、ES2016、ES2017 等等,而 ES2015 则是正式名称,特指该年发布的正式版本的语言标准。本书中提到 ES6 的地方,一般是指 ES2015 标准,但有时也是泛指“下一代 JavaScript 语言”。

语法提案的批准流程

任何人都可以向标准委员会(又称 TC39 委员会)提案,要求修改语言标准。

一种新的语法从提案到变成正式标准,需要经历五个阶段。每个阶段的变动都需要由 TC39 委员会批准。

  1. -Stage 0 - Strawman(展示阶段)
  2. -Stage 1 - Proposal(征求意见阶段)
  3. -Stage 2 - Draft(草案阶段)
  4. -Stage 3 - Candidate(候选人阶段)
  5. -Stage 4 - Finished(定案阶段)

一个提案只要能进入 Stage 2,就差不多肯定会包括在以后的正式标准里面。

ECMAScript 的历史

ES6 从开始制定到最后发布,整整用了15 年。

前面提到,ECMAScript 1.0 是 1997 年发布的,接下来的两年,连续发布了 ECMAScript 2.0(1998 年 6 月)和 ECMAScript 3.0(1999 年 12 月)。3.0 版是一个巨大的成功,在业界得到广泛支持,成为通行标准,奠定了 JavaScript 语言的基本语法,以后的版本完全继承。直到今天,初学者一开始学习 JavaScript,其实就是在学 3.0 版的语法。

2000 年,ECMAScript 4.0 开始酝酿。这个版本最后没有通过,但是它的大部分内容被 ES6 继承了。因此,ES6 制定的起点其实是 2000 年。

为什么 ES4 没有通过呢?因为这个版本太激进了,对 ES3 做了彻底升级,导致标准委员会的一些成员不愿意接受。ECMA 的第 39 号技术专家委员会(Technical Committee 39,简称 TC39)负责制订 ECMAScript 标准,成员包括 Microsoft、Mozilla、Google 等大公司。

2007 年 10 月,ECMAScript 4.0 版草案发布,本来预计次年 8 月发布正式版本。但是,各方对于是否通过这个标准,发生了严重分歧。以 Yahoo、Microsoft、Google 为首的大公司,反对 JavaScript 的大幅升级,主张小幅改动;以 JavaScript 创造者 Brendan Eich 为首的 Mozilla 公司,则坚持当前的草案。

2008 年 7 月,由于对于下一个版本应该包括哪些功能,各方分歧太大,争论过于激烈,ECMA 开会决定,中止 ECMAScript 4.0 的开发,将其中涉及现有功能改善的一小部分,发布为 ECMAScript 3.1,而将其他激进的设想扩大范围,放入以后的版本,由于会议的气氛,该版本的项目代号起名为 Harmony(和谐)。会后不久,ECMAScript 3.1 就改名为 ECMAScript 5。

2009 年 12 月,ECMAScript 5.0 版正式发布。Harmony 项目则一分为二,一些较为可行的设想定名为 JavaScript.next 继续开发,后来演变成 ECMAScript 6;一些不是很成熟的设想,则被视为 JavaScript.next.next,在更远的将来再考虑推出。TC39 委员会的总体考虑是,ES5 与 ES3 基本保持兼容,较大的语法修正和新功能加入,将由 JavaScript.next 完成。当时,JavaScript.next 指的是 ES6,第六版发布以后,就指 ES7。TC39 的判断是,ES5 会在 2013 年的年中成为 JavaScript 开发的主流标准,并在此后五年中一直保持这个位置。

2011 年 6 月,ECMAScript 5.1 版发布,并且成为 ISO 国际标准(ISO/IEC 16262:2011)。

2013 年 3 月,ECMAScript 6 草案冻结,不再添加新功能。新的功能设想将被放到 ECMAScript 7。

2013 年 12 月,ECMAScript 6 草案发布。然后是 12 个月的讨论期,听取各方反馈。

2015 年 6 月,ECMAScript 6 正式通过,成为国际标准。从 2000 年算起,这时已经过去了 15 年。