ES6语法(一)块级作用域、字符串

533 阅读10分钟

最近在阅读《深入立即ECMAScript6》这本书,打算把自己学到的、用到的和理解的来说一说。 我会基本按照书上的顺序来说,大概会分为三到四篇的样子,不会全部都写,我只会挑一些常用的,或者我觉得比较有意思的来写。

块级作用域绑定(let、const)

为何要引入块级作用域

在ES6之前,JavaScript中是没有块级作用域的概念的,只有全局作用域和块级作用域。也就是说在块级作用域声明与外部同名的变量,会覆盖掉外部的变量。而没有块级作用域的话,主要会有以下两个问题。

  1. 块级作用域内部变量泄露
    var i = 1;
    console.log(i)                  // 1
    for(var i =0;i<10;i++){             
        console.log(i);             // 0...9
    }
    console.log(i)                  // 10
  1. 循环中创建函数保留了相同的变量引用
    var funcs = [];
    
    for(var i = 0;i < 10;i++){
        funcs.push(function (){
            console.log(i);
        });
    }
    
    funcs.forEach(function(func){
        func();                     // 输出10次数字10
    });
    

上面两个问题都是没有块级作用域导致的,但是ES6发生了改变,为我们引入了块级作用域。

let声明

let声明的用法

基本与var相同,但是它的生命周期在块级作用域内部,一旦出去块级作用域便会消失。同时不能重复声明同名变量

示例

基本用法

    var i = 0;
    if(true){
        let i = 100000;
    }
    console.log(i);                  // 0

循环中的let声明

    var i = 1;
    console.log(i)                  // 1
    for(let i =0;i<10;i++){             
        console.log(i);             // 0...9
    }
    console.log(i)                  // 1
运行机制

其实let在每次创建的时候都会创建一个新的变量,并以之前迭代中同名变量的值将其初始化。简而言之就是let每次都会创建值的副本,而不是像var一样修改引用的值。

当第一次执行for循环的时候,会新建一个变量i(在内存中开辟一块空间),然后将0赋值给这个变量,继续执行下面的语句;当第二次执行的时候,又会新建一个变量i(在内存中重新开辟一个区域),之后再将原来i的值赋值给新的i,继续在执行下面的语句。循环往复,直到循环的条件不满足为止。

const声明

const声明的用法

与let的用法基本也是相同的,但是它一旦被定义就不能被修改。当然,说的是原始值,如果你使用的是引用,你可以更改引用内部的值,但是你无法更改对引用的绑定。而且const声明必须被初始化,不然就会报错。

示例

基本语法

    const name;                     //错误,未初始化
    const maxItems = 30;
    maxItems = 100;                 //报错,不能更改绑定

循环中的const声明

    var funcs = [];
    
    //完成一次迭代之后会报错
    for(const i =0;i<10;i++){
        funcs.push(function(){
            console.log(i);
        });
    }

这里会报错的原因是变量i被const声明为常量,当迭代到i++是,这条语句想要修改常量i的值,所以会报错。还有神奇的地方,让我们来看看下面的例子

    var funcs = [];
    person = {
      name : 'Godfrey',
      age : 20,
      sex : 'male'
    };
    
    for(const key in person){
        funcs.push(function(){
            console.log(key);
        });
    }
    
    funcs.forEach(function(func){
        func();                         // 输出name、age、sex
    });
    

你会发现这里和我们上面说的有些不一样了,不是说const是常量吗,不能被修改吗?为什么在这个for-in循环中可以被正确执行。
别着急,是这样的,在for-in和for-of循环中,每次迭代不会修改已有的绑定,而是会创建一个新的绑定,就像我上面讲的let在循环中的表现是一样的。也就是说在某些情况下,const会和let表现是完全相同的。

临时死区(TDZ)

在引入let声明和const声明的同时,也带来了临时死区的概念。

简单的说,临时死去就是在变量未初始化之前不能使用。下面我们来看例子

    if(true){
        console.log(typeof a);          // 引用错误
        let a = 1;
    }

JavaScript引擎在扫描到变量的时候,如果遇到的是var声明的变量,就发生变量提升,如果是let、const声明的变量,就加入“临时死区”。

tips:在块级作用域之外声明的访问let、const声明的同名变量不会报错,也就是说“临时死区”只作用在当前块级作用域。

字符串和正则表达式

ES6引入了一些有关字符串方面的新方法和新特性。主要有以下几个方面,字符串、正则表达式、模板字面量

字符串新特性

  • 更好的Unicode支持
  • codePointAt()方法——charCodeAt()的Unicode-16版本
    接受一个编码单元的位置而非字符串的位置作为参数,返回与字符串给定位置的码位。
    let test = "𠮷a"
    
    console.log(test.charCodeAt(0));    // 55362
    console.log(test.charCodeAt(1))     // 57271
    console.log(test.charCodeAt(2))     // 97
    
    console.log(test.codePointAt(0));    // 134071
    console.log(test.codePointAt(1))     // 57271
    console.log(test.codePointAt(2))     // 97
    
charCodeAt(0)返回的是位置0处的第一个编码单元,但是这个往往不是我们想要的结果,而codePointAt(0)返回的是第一个字的完整编码单元。
  • String.fromCodePoint()方法
    通过这个方法我们可以根据指定码位来生成一个字符
    console.log(String.fromCodePoint(134071));      //𠮷
  • normalize()方法 这个方法是暂且不用管他,主要是涉及到国际化方面

字符串新方法

  • includes()
    如果在字符串检测到指定文本则返回true,否则返回false。
  • startsWith()
    如果在字符川的起始部分检测到指定文本则返回true,否则返回false。
  • endsWith()
    如果在字符串的结尾部分检测到指定文本则返回true,否则返回false。
    let test = 'Hello world';
    
    console.log(test.includes("Hello"));        // true
    console.log(test.includes("a"));            // false
    console.log(test.startsWith("Hello"));      // true
    console.log(test.startsWith("Hello",4));    // false
    console.log(test.endsWith("d"));            // true
    console.log(test.startsWith("Hello"));      // false
    console.log(test.endsWith("o",8));          // true      
    

以上三个方法都会接受两个参数。第一个是要检测的文本内容,第二个是一开始搜索的位置的索引。includes和startsWith都会从索引的位置开始搜索,而endsWith会从字符串长度减去这个索引值的位置开始匹配。

上面就是就是ES6字符串主要的新增方法。

正则表达式新特性

ES6为了更好的满足开发中对字符串的操作,同时也增强了正则表达式。

  • u修饰符
    在正则表达式添加了u修饰符时,正则表达式的操作就从编码单元变为字符模式。
    let test = "𠮷";
    
    console.log(text.length);                   // 2
    console.log(/^.$/.test(test));              // false
    console.log(/^.$/u.test(test));             // true
  • y修饰符
    y修饰符会影响正则表达式搜索过程中的lastIndex属性,如果使用了y修饰符,当开始匹配的时候,便会从latIndex开始进行。如果在指定位置没能匹配成功,则继续停止匹配。
    var text = "kidd1 kidd2 kidd3";
    var pattern = /kidd\d\s?/;
    var result = pattern.exec(text);
    var globalPattern = /kidd\d\s?/g;
    var globalResult = globalPattern.exec(text);
    var stickyPattern = /kidd\d\s?/y;
    var stickReslut = stickyPattern.exec(text);
    
    console.log(result[0]);                     // "kidd1 "
    console.log(globalResult[0]);               // "kidd1 "
    console.log(stickReslut[0]);                // "kidd1 "
    
    pattern.lastIndex = 1;
    globalPattern.lastIndex = 1;
    stickyPattern.lastIndex = 1;
    
    result = pattern.exec(text);
    globalResult = globalPattern.exec(text);
    stickReslut = stickyPattern.exec(text);
    
    console.log(result[0]);                     // "kidd1 "
    console.log(globalResult[0]);               // "kidd2 "
    console.log(stickReslut[0]);                // null
    

tips:

  1. y修饰符只在字符串对象中起作用,对字符串的方法无效。
  2. 还有就是使用^字符的时候,只会在字符串的起始位置和多行模式的首行进行匹配。当lastIndex为0时,完全没问题,会向普通正则表达式一般无二;但是当lastIndex不为0时,就会永远也匹配不到正确结果
  • 修正了正则表达式构造函数中复制的错误
    在ES6之前,使用构造函数创建正则表达式有一个小bug,就是使用正则表达式声明正则表达式,第一个参数为正则表达式,第二个参数可以为正则表达式添加一些修饰符。但是当第一个参数是另一个正则表达式的引用时,再传入第二个参数,ES5就会报错。现在ES6修正了这个问题
    var regex1 = /ab/g;
    //在ES5中时会抛出错误的。但在ES6中就是正常的。
    var regex2 = new Regex2(regex1,"i");
    
    console.log(regex1.toString());         // "/ab/g"
    console.log(regex2.toString());         // "/ab/i"

  • flags属性
    ES6还为正则表达式对象新增了一个属性——flags,是帮助我们更快捷的访问正则表达式中的修饰符。通过正则表达式对象调用它,她会返回该正则表达式的所有修饰符。
    var regex = /ab/g;
    
    console.log(regex.flags);               // "g"

模板字面量

这是ES6引入的全新的语法。主要是为了解决字符串多行模式、占位符和提供更强大的字符串操作。

基础语法

模板字面量基础的使用方法看起来与字符串字面量没有大的区别,无非是将字符串字面量的引号(单、双都可以)换成了反撇号(`),最直观的特点是能支持多行字符串。

    var message = `123
    456
    789`;
    
    console.log(message);           //"123
                                    // 456
                                    // 789"
                                    

上面是模板字面量最基本的用法,看起来好像没什么特别强大的地方,ES5也完全能做到,就是麻烦一点,需要手动拼接。好!接下来才是模板字面量真正强大的地方(其实,是否强大与否主要还是看应用,毕竟编程还是偏向应用科学的)。

字符串占位符

字符串占位符的基础语法是${},中间可以包含任意的JavaScript表达式。字符串占位符为你提供了将“表示式”嵌入到字符串中的功能,并且她会计算计算出结果。话不多说,来看个简单的例子。

    let a = 123,
        b = 456;
    message = `Sum is ${a + b}`;
    
    console.log(message);           // "Sum is 579";

字符串占位符也是支持嵌套的。

标签模板

首先我们要说明什么是标签,标签就是在模板字面量第一个反撇号(`)之前标注的字符串。当然这个字符串不需要添加引号。当然,如果这个标签仅仅是个字符串是没什么作用的,如果它可以是一个函数对象的引用,那就很强大了,这样的话,我们在处理同类型字符串,直接在它们前面添加一个tag,就很方便了。

    function tag(literals,...substitutions){
        let result = "";
        
        for(let i = 0;i< substitutions.length; i++){
            result += literals[i];
            result += substitutions[i];
        };
        
        result += literals[literals.length-1];
        
        return result;
    }
    
    let a = 123,
        b = 456,
        message = tag `Sum is ${a+b}`;
    
    console.log(message);           // "Sum is 579";

上面我们有tag标签模拟了模板字面量默认的行。我们可以看到函数中有两个参数,其实两个都是数组。

  • literals
    literals参数实际上是一个数组,你可以将他看成是传入了占位符以外的所有字符串,只不过将那些字符串根据占位符分割开来这样就形成了一个数组,存放在literals中。还有一点就是,如果模板字面量的第一位是一个占位符,那literals数组的第一个值就是空字符串。保证了literals总是比substitutions多一个。
  • substitutions
    既然有存放字符串的数组了,那就必须有存放字符串占位符的地方,那就是substitutions数组了。它其中存放了经过计算后的表达式的值。

上面是有关字符串、正则表达式的ES6新增或修改的内容。

本篇文章的内容都是我借鉴的书本上面的知识,再添加了一些自己的理解。可能会有错误的地方,如果您看到了,那请你指出来。如果对文章的内容哪里有不明白的地方,可以在下面留言,我会进行更加细致的说明。