块级作用域、模板字符串(前端进阶1.2)

475 阅读4分钟

变量的声明

Es6 中引入了 letconst 来声明变量解决 var 的一些问题

var

使用 var 声明变量的问题是:变量可以重复声明;变量重复的声明可能会导致一些预料不到的问题;

var a = 0;
var a = 1;
console.log(a);//结果返回1
  • var 没有块级作用域

es6 之前是没有块级作用域的概念,只有全局作用域和函数作用域,块级作用域使变量只在当前代码块生效如 if、 switch 、for …等等。

if(true){
	var a = 1;
}
console.log(a);//1 ,es5正常打印,没有块级作用域
  • var 不能定义常量

es5 中没有常量或者说不能直接定义常量,要定义常量,如下

// const cconst = 1;
// console.log(cconst); // 输出结果是1

//上面的代码 可以用下面的代码进行实现
Object.defineProperty(window,'cconst',{writable:false,value:1});
// 然后我们尝试修改看看
cconst = 3;
console.log(cconst);//发现输出的结果是1,并不能修改值

还是比较麻烦,在es6中使用const简单解决问题

  • var 存在变量提升 ES6 之前存在变量提升,如下:
 console.log(a);
 var a = 34; //结果返回undifind

这里没有报错如 a 未定义什么的,这是因为变量提升机制相对于进行了下面操作

var x;
console.log(x);//undifind
x=2;//变量提升不会赋值

let

下面我们通过 let 声明变量

  • 变量重复声明
let a = 0;
let a = 1;
console.log(a);

直接报错,let 不允许变量重复声明,相对es5更严谨。

  • 块级作用域的问题 可以将块级作用域理解为一个代码块,如 ifforswitch 等等;
if(true){let a = 1;}
console.log(a);

报错a未定义,这里块级作用域就体现出来了,由于es6增加了块级作用域的特性,所以 if 里面的 a 与 console 的 a 完全是两个作用域,所以出现下图。 在来看一到块级作用域的案例:有如下需求,定义4个按钮,使用for循环,每次按钮被点击输出i:

var btn = document.getElementsByTagName('button');
for(var i=0;i<btn.length;i++){
  btn[i].onclick=function(){
    console.log(i)
  }	
}

上面代码并不能正常运行,当我们点击其中的任何一个按钮时,总是会输出4,而不是0,1,2,3;

在没有使用es6我们可能会通过闭包来解决上面的问题;

var btn = document.getElementsByTagName('button');
for(var i=0;i<btn.length;i++){
  !function(i){
    btn[i].onclick=function(){
      console.log(i)
    }	
  }(i)
}

正常输出i实现原理也简单,通过循环创建了4个闭包函数,形成了单独的4个函数作用域。 既然是一个作用域的方式解决,那我们知道es6中有块级作用域的概念,我们将 var 换成 let 试试:

var btn = document.getElementsByTagName('button');
for(let i=0;i<btn.length;i++){
  btn[i].onclick=function(){
    console.log(i)
  }	
}

结果也正常输出,相对于上面的方式简单太多了

const

const声明常量

const PI = 1.34;
console.log(PI)//正常输出
PI = 34;
console.log(PI);//尝试修改报错

模板字符串

模板字面量 是允许嵌入表达式的字符串字面量。你可以使用多行字符串和字符串插值功能。它们在 ES2015 规范的先前版本中被称为 “模板字符串”

语法

`string text`

`string text line 1
string text line 2 `

`string text ${expression} string text`

tab `string text ${expression} string text`

描述

模板字符串使用反引号 (` `) 来代替普通字符串中的用双引号和单引号。模板字符串可以包含特定语法(${expression})的占位符。占位符中的表达式和周围的文本会一起传递给一个默认函数,该函数负责将所有的部分连接起来,如果一个模板字符串由表达式开头,则该字符串被称为带标签的模板字符串,该表达式通常是一个函数,它会在模板字符串处理后被调用,在输出最终结果前,你都可以通过该函数来对模板字符串进行操作处理。在模版字符串内使用反引号(`)时,需要在它前面加转义符(\)

`\`` === "`" // --> true

多行字符串

在新行中插入的任何字符都是模板字符串中的一部分,使用普通字符串,你可以通过以下的方式获得多行字符串:

console.log('string text line 1\n' +
'string text line 2');
// "string text line 1
// string text line 2"

要获得同样效果的多行字符串,只需使用如下代码:

console.log(`string text line 1
string text line 2`);
// "string text line 1
// string text line 2"

插入表达式

在普通字符串中嵌入表达式,必须使用如下语法:

var a = 5;
var b = 10;
console.log('Fifteen is ' + (a + b) + ' and\nnot ' + (2 * a + b) + '.');
// "Fifteen is 15 and
// not 20."

现在通过模板字符串,我们可以使用一种更优雅的方式来表示:

var a = 5;
var b = 10;
console.log(`Fifteen is ${a + b} and
not ${2 * a + b}.`);
// "Fifteen is 15 and
// not 20."

嵌套模板

在某些时候,嵌套模板是具有可配置字符串的最简单也是更可读的方法。 在模板中,只需在模板内的占位符 ${ } 内使用它们,就可以轻松地使用内部反引号。 例如,如果条件 a 是真的,那么返回这个模板化的文字。

ES5:

var classes = 'header'
classes += (isLargeScreen() ?
   '' : item.isCollapsed ?
     ' icon-expander' : ' icon-collapser');

在ES2015中使用模板文字而没有嵌套:

const classes = `header ${ isLargeScreen() ? '' :
    (item.isCollapsed ? 'icon-expander' : 'icon-collapser') }`;

在ES2015的嵌套模板字面量中:

const classes = `header ${ isLargeScreen() ? '' :
 `icon-${item.isCollapsed ? 'expander' : 'collapser'}` }`;

带标签的模板字符串

更高级的形式的模板字符串是带标签的模板字符串。标签使您可以用函数解析模板字符串。标签函数的第一个参数包含一个字符串值的数组。其余的参数与表达式相关。最后,你的函数可以返回处理好的的字符串(或者它可以返回完全不同的东西 , 如下个例子所述)。用于该标签的函数的名称可以被命名为任何名字。

var person = 'Mike';
var age = 28;

function myTag(strings, personExp, ageExp) {

  var str0 = strings[0]; // "that "
  var str1 = strings[1]; // " is a "

  // There is technically a string after
  // the final expression (in our example),
  // but it is empty (""), so disregard.
  // var str2 = strings[2];

  var ageStr;
  if (ageExp > 99){
    ageStr = 'centenarian';
  } else {
    ageStr = 'youngster';
  }

  return str0 + personExp + str1 + ageStr;

}

var output = myTag`that ${ person } is a ${ age }`;

console.log(output);
// that Mike is a youngster

正如下面例子所展示的,标签函数并不一定需要返回一个字符串。

function template(strings, ...keys) {
  return (function(...values) {
    var dict = values[values.length - 1] || {};
    var result = [strings[0]];
    keys.forEach(function(key, i) {
      var value = Number.isInteger(key) ? values[key] : dict[key];
      result.push(value, strings[i + 1]);
    });
    return result.join('');
  });
}

var t1Closure = template`${0}${1}${0}!`;
t1Closure('Y', 'A');  // "YAY!"
var t2Closure = template`${0} ${'foo'}!`;
t2Closure('Hello', {foo: 'World'});  // "Hello World!"

原始字符串

在标签函数的第一个参数中,存在一个特殊的属性raw ,我们可以通过它来访问模板字符串的原始字符串,而不经过特殊字符的替换。

function tag(strings) {
  console.log(strings.raw[0]);
}

tag`string text line 1 \n string text line 2`;
// logs "string text line 1 \n string text line 2" ,
// including the two characters '\' and 'n'

另外,使用String.raw() 方法创建原始字符串和使用默认模板函数和字符串连接创建是一样的。

var str = String.raw`Hi\n${2+3}!`;
// "Hi\n5!"

str.length;
// 6

str.split('').join(',');
// "H,i,\,n,5,!"

带标签的模版字面量及转义序列

自ES2016起,带标签的模版字面量遵守以下转义序列的规则:

  • Unicode字符以"\u"开头,例如\u00A9
  • Unicode码位用"\u{}"表示,例如\u{2F804}
  • 十六进制以"\x"开头,例如\xA9
  • 八进制以""和数字开头,例如\251 这表示类似下面这种带标签的模版是有问题的,因为对于每一个ECMAScript语法,解析器都会去查找有效的转义序列,但是只能得到这是一个形式错误的语法:
latex`\unicode`
// 在较老的ECMAScript版本中报错(ES2016及更早)
// SyntaxError: malformed Unicode character escape sequence

ES2018关于非法转义序列的修订

带标签的模版字符串应该允许嵌套支持常见转义序列的语言(例如DSLsLaTeX)。ECMAScript提议模版字面量修订(第4阶段,将要集成到ECMAScript 2018标准) 移除对ECMAScript在带标签的模版字符串中转义序列的语法限制。

不过,非法转义序列在"cooked"当中仍然会体现出来。它们将以 undefined 元素的形式存在于"cooked"之中:

function latex(str) {
 return { "cooked": str[0], "raw": str.raw[0] }
}

latex`\unicode`

// { cooked: undefined, raw: "\\unicode" }

值得注意的是,这一转义序列限制只对带标签的模板字面量移除,而不包括不带标签的模板字面量:

let bad = `bad escape sequence: \unicode`;