ES6 简介(一)

news2024/11/13 13:11:16

文章目录

  • ES6 简介(一)
    • 一、 概述
      • 1、 导读
      • 2、 Babel 转码器
        • 2.1 是什么
        • 2.2 配置文件 .babelrc
        • 2.3 命令行转码
        • 2.4 babel-node
        • 2.5 @babel/register
        • 2.6 polyfill
        • 2.7 浏览器环境
    • 二、 变量
      • 1、 let
      • 2、 const
      • 3、 ES6 声明变量
      • 4、 顶层对象的属性
      • 5、 globalThis 对象
    • 三、 解构和赋值
      • 1、 数组的解构赋值
        • 1.1 语法
        • 1.2 默认值
      • 2、 对象的解构赋值
        • 2.1 语法
        • 2.2 默认值
      • 3、 字符串的解构赋值
      • 4、 函数参数的解构赋值
      • 5、 用途
    • 四、 字符串扩展
      • 1、 字符的 Unicode 表示法
      • 2、 字符串的遍历器接口
      • 3、 模板字符串
      • 4、 标签模板
      • 5、 新增方法

ES6 简介(一)

一、 概述

1、 导读

ECMAScript 6(ES6) 目前基本成为业界标准,它的普及速度比 ES5 要快很多,主要原因是现代浏览器对 ES6 的支持相当迅速,尤其是 Chrome 和 Firefox 浏览器,已经支持 ES6 中绝大多数的特性。

ES6经过持续几年的磨砺,它已成为 JS 有史以来最实质的升级,特性涵盖范围甚广, 小到受欢迎的语法糖,例如箭头函数(arrow functions)和简单的字符串插值(string interpolation),大到烧脑的新概念,例如代理(proxies)和生成器(generators);它将彻底改变程序员们编写JS代码的方式。

2、 Babel 转码器

2.1 是什么

Babel 是一个广泛使用的 ES6 转码器,可以将 ES6 代码转为 ES5 代码,从而在老版本的浏览器执行。这意味着,你可以用 ES6 的方式编写程序,又不用担心现有环境是否支持。下面是一个例子。

// 转码前
input.map(item => item + 1);
// 转码后
input.map(function (item) {
  return item + 1;
});

安装 Babel :npm install --save-dev @babel/core

2.2 配置文件 .babelrc

Babel 的配置文件是.babelrc,存放在项目的根目录下。使用 Babel 的第一步,就是配置这个文件。

该文件用来设置转码规则和插件,基本格式如下。

{
  "presets": [],
  "plugins": []
}

presets字段设定转码规则,官方提供以下的规则集,你可以根据需要安装。

# 最新转码规则
npm install --save-dev @babel/preset-env
# react 转码规则
npm install --save-dev @babel/preset-react

然后,将这些规则加入 .babelrc。

{
    "presets": [
      "@babel/env",
      "@babel/preset-react"
    ],
    "plugins": []
}

注意,以下所有 Babel 工具和模块的使用,都必须先写好 .babelrc。

2.3 命令行转码

Babel 提供命令行工具@babel/cli,用于命令行转码。 它的安装命令:npm install --save-dev @babel/cli

使用语法:

# 转码结果输出到标准输出
npx babel example.js
# 转码结果写入一个文件
#  --out-file 或 -o 参数指定输出文件
npx babel example.js --out-file compiled.js
# 或者
npx babel example.js -o compiled.js
# 整个目录转码
# --out-dir 或 -d 参数指定输出目录
npx babel src --out-dir lib
#  或者
npx babel src -d lib
# -s 参数生成source map文件
npx babel src -d lib -s

2.4 babel-node

@babel/node模块的babel-node命令,提供一个支持 ES6 的 REPL 环境。它支持 Node 的 REPL 环境的所有功能,而且可以直接运行 ES6 代码。 首先,安装这个模块:npm install --save-dev @babel/node

然后,执行 babel-node 就进入 REPL 环境。

npx babel-node
# >(x => x * 2)(1)
# >2

babel-node 命令可以直接运行 ES6 脚本。将上面的代码放入脚本文件 es6.js ,然后直接运行。

npx babel-node es6.js

2.5 @babel/register

@babel/register模块改写require命令,为它加上一个钩子。此后,每当使用 require 加载 .js 、.jsx 、.es 和 .es6 后缀名的文件,就会先用 Babel 进行转码。

npm install --save-dev @babel/register

使用时,必须首先加载 @babel/register。

// index.js
require('@babel/register');
require('./es6.js');

@babel/register只会对require命令加载的文件转码,而不会对当前文件转码。另外,由于它是实时转码,所以只适合在开发环境使用。

2.6 polyfill

Babel 默认只转换新的 JavaScript 句法(syntax),而不转换新的API,比如 Iterator 、Generator 、Set 、Map 、Proxy 、Reflect 、Symbol 、Promise 等全局对象,以及一些定义在全局对象上的方法(比如 Object.assign )都不会转码。

举例来说,ES6 在 Array 对象上新增了Array.from方法。Babel 就不会转码这个方法。如果想让这个方法运行,可以使用 core-js 和 regenerator-runtime (后者提供 generator 函数的转码),为当前环境提供一个垫片。

安装命令:npm install --save-dev core-js regenerator-runtime

然后,在脚本头部,加入如下两行代码。

import 'core-js';
import 'regenerator-runtime/runtime';
// 或者
require('core-js');
require('regenerator-runtime/runtime);

Babel 默认不转码的 API 非常多,详细清单可以查看 babel-plugin-transform-runtime 模块的 definitions.js 文件。

2.7 浏览器环境

Babel 也可以用于浏览器环境,使用@babel/standalone模块提供的浏览器版本,将其插入网页。

<script src="https://unpkg.com/@babel/standalone/babel.min.js" rel="external nofollow" ></script>
<script type="text/babel">
// Your ES6 code
</script>

注意,网页实时将 ES6 代码转为 ES5,对性能会有影响。生产环境需要加载已经转码完成的脚本。

Babel 提供一个REPL 在线编译器,可以在线将 ES6 代码转为 ES5 代码。转换后的代码,可以直接作为 ES5 代码插入网页运行。

二、 变量

1、 let

ES6 新增了let命令,用来声明变量。它的用法类似于var,但是所声明的变量,只在let命令所在的代码块内有效。

{
  let a = 10;
  var b = 1;
}
a // ReferenceError: a is not defined.
b // 1

上面代码在代码块之中,分别用let和var声明了两个变量。然后在代码块之外调用这两个变量,结果let声明的变量报错,var声明的变量返回了正确的值。这表明,let声明的变量只在它所在的代码块有效。

for循环的计数器,就很合适使用let命令。

for (let i = 0; i < 10; i++) {
  // ...
}
console.log(i);
// ReferenceError: i is not defined

for循环还有一个特别之处,就是设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域。

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

var命令会发生**“变量提升”**现象,即变量可以在声明之前使用,值为undefined。这种现象多多少少是有些奇怪的,按照一般的逻辑,变量应该在声明语句之后才可以使用。

为了纠正这种现象,let命令改变了语法行为,它所声明的变量一定要在声明后使用,否则报错。

// var 的情况
console.log(foo); // 输出undefined
var foo = 2;

// let 的情况
console.log(bar); // 报错ReferenceError
let bar = 2;

在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)。

let不允许在相同作用域内,重复声明同一个变量。

// 报错
function func() {
  let a = 10;
  var a = 1;
}
// 报错
function func() {
  let a = 10;
  let a = 1;
}

同时,ES6 还可以在块级作用域声明函数

  • 允许在块级作用域内声明函数。
  • 函数声明类似于 var,即会提升到全局作用域或函数作用域的头部。
  • 同时,函数声明还会提升到所在的块级作用域的头部。

注意,上面三条规则只对 ES6 的浏览器实现有效,其他环境的实现不用遵守,还是将块级作用域的函数声明当作let处理。

考虑到环境导致的行为差异太大,应该避免在块级作用域内声明函数。如果确实需要,也应该写成函数表达式,而不是函数声明语句。

ES6 的块级作用域必须有大括号,如果没有大括号,JavaScript 引擎就认为不存在块级作用域。

2、 const

const声明一个只读常量。一旦声明,常量的值就不能改变。

const PI = 3.1415;
PI // 3.1415
PI = 3;
// TypeError: Assignment to constant variable.

注意:

  • const 声明的变量不得改变值,这意味着,const 一旦声明变量,就必须立即初始化,不能留到以后赋值。
  • const 的作用域与 let 命令相同:只在声明所在的块级作用域内有效。
  • const 命令声明的常量也是不提升,同样存在暂时性死区,只能在声明的位置后使用
  • const 声明的常量,也与let一样不可重复声明。

本质 const 实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心。

const foo = {};
// 为 foo 添加一个属性,可以成功
foo.prop = 123;
foo.prop // 123
// 将 foo 指向另一个对象,就会报错
foo = {}; // TypeError: "foo" is read-only

3、 ES6 声明变量

ES5 只有两种声明变量的方法:var命令和function命令。ES6 除了添加letconst命令,后面章节还会提到,另外两种声明变量的方法:import命令和class命令。所以,ES6 一共有 6 种声明变量的方法。

4、 顶层对象的属性

顶层对象,在浏览器环境指的是window对象,在 Node 指的是global对象ES5之中,顶层对象的属性与全局变量是等价的。

window.a = 1;
a // 1
a = 2;
window.a // 2

顶层对象的属性与全局变量挂钩,被认为是 JavaScript 语言最大的设计败笔之一。

这样的设计带来了几个很大的问题:

  1. 没法在编译时就报出变量未声明的错误,只有运行时才能知道(因为全局变量可能是顶层对象的属性创造的,而属性的创造是动态的)
  2. 程序员很容易不知不觉地就创建了全局变量(比如打字出错
  3. 顶层对象的属性是到处可以读写的,这非常不利于模块化编程。另一方面,window对象有实体含义,指的是浏览器的窗口对象,顶层对象是一个有实体含义的对象,也是不合适的。

ES6 为了改变这一点:

  1. 为了保持兼容性,var 命令和 function 命令声明的全局变量,依旧是顶层对象的属性
  2. let 命令、const 命令、class 命令声明的全局变量,不属于顶层对象的属性。也就是说,从 ES6 开始,全局变量将逐步与顶层对象的属性脱钩。
var a = 1;
// 如果在 Node 的 REPL 环境,可以写成 global.a
// 或者采用通用方法,写成 this.a

window.a // 1
let b = 1;
window.b // undefined

上面代码中,全局变量 a 由 var 命令声明,所以它是顶层对象的属性;全局变量 b 由 let 命令声明,所以它不是顶层对象的属性,返回 undefined 。

5、 globalThis 对象

JavaScript 语言存在一个顶层对象,它提供全局环境(即全局作用域),所有代码都是在这个环境中运行。但是,顶层对象在各种实现里面是不统一的。

  • 浏览器里面,顶层对象是window,但 Node 和 Web Worker 没有window。
  • 浏览器和 Web Worker 里面,self也指向顶层对象,但是 Node 没有self。
  • Node 里面,顶层对象是global,但其他环境都不支持。

同一段代码为了能够在各种环境,都能取到顶层对象,现在一般是使用this变量,但是有局限性。

  • 全局环境中,this会返回顶层对象。但是,Node 模块和 ES6 模块中,this返回的是当前模块。
  • 函数里面的this,如果函数不是作为对象的方法运行,而是单纯作为函数运行,this会指向顶层对象。但是,严格模式下,这时this会返回undefined。
  • 不管是严格模式,还是普通模式,new Function(‘return this’)(),总是会返回全局对象。但是,如果浏览器用了 CSP(Content Security Policy,内容安全策略),那么eval、new Function这些方法都可能无法使用。

综上所述,很难找到一种方法,可以在所有情况下,都取到顶层对象。下面是两种勉强可以使用的方法。

// 方法一
(typeof window !== 'undefined'
   ? window
   : (typeof process === 'object' &&
      typeof require === 'function' &&
      typeof global === 'object')
     ? global
     : this);
// 方法二
var getGlobal = function () {
  if (typeof self !== 'undefined') { return self; }
  if (typeof window !== 'undefined') { return window; }
  if (typeof global !== 'undefined') { return global; }
  throw new Error('unable to locate global object');
};

ES2020 在语言标准的层面,引入 globalThis 作为顶层对象。也就是说,任何环境下,globalThis都是存在的,都可以从它拿到顶层对象,指向全局环境下的this。

垫片库 global-this 模拟了这个提案,可以在所有环境拿到 globalThis 。ES6 新增了let命令,用来声明变量。它的用法类似于var,但是所声明的变量,只在let命令所在的代码块内有效。

三、 解构和赋值

1、 数组的解构赋值

1.1 语法

ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。

// ES5
let a = 1;
let b = 2;
let c = 3;

// ES,可以这样写
let [a, b, c] = [1, 2, 3];

本质上,这种写法属于**“模式匹配”**,只要等号两边的模式相同,左边的变量就会被赋予对应的值。下面是一些使用嵌套数组进行解构的例子。

let [foo, [[bar], baz]] = [1, [[2], 3]];
foo // 1
bar // 2
baz // 3

let [ , , third] = ["foo", "bar", "baz"];
third // "baz"

let [x, , y] = [1, 2, 3];
x // 1
y // 3

let [head, ...tail] = [1, 2, 3, 4];
head // 1
tail // [2, 3, 4]

let [x, y, ...z] = ['a'];
x // "a"
y // undefined
z // []

另一种情况是不完全解构,即等号左边的模式,只匹配一部分的等号右边的数组。这种情况下,解构依然可以成功。

let [x, y] = [1, 2, 3];
x // 1
y // 2
let [a, [b], d] = [1, [2, 3], 4];
a // 1
b // 2
d // 4

对于Set 结构,也可以使用数组的解构赋值。

let [x, y, z] = new Set(['a', 'b', 'c']);
x // "a"

事实上,只要某种数据结构具有Iterator接口,都可以采用数组形式的解构赋值。

function* fibs() {  // Generator 函数
  let a = 0;
  let b = 1;
  while (true) {
    yield a;
    [a, b] = [b, a + b];
  }
}
let [first, second, third, fourth, fifth, sixth] = fibs();
sixth // 5

1.2 默认值

解构赋值允许有默认值

let [foo = true] = [];
foo // true

let [x, y = 'b'] = ['a']; // x='a', y='b'
let [x, y = 'b'] = ['a', undefined]; // x='a', y='b'

注意,ES6 内部使用严格相等运算符(===),判断一个位置是否有值。所以,只有当一个数组成员严格等于undefined,默认值才会生效。

如果默认值是一个表达式,那么这个表达式是惰性求值的,即只有在用到的时候,才会求值。

function f() {
  console.log('aaa');
}
let [x = f()] = [1];

上面代码中,因为x能取到值,所以函数f根本不会执行。上面的代码其实等价于下面的代码。

let x;
if ([1][0] === undefined) {
  	x = f();
} else {
  	x = [1][0];
}

默认值可以引用解构赋值的其他变量,但该变量必须已经声明。

let [x = 1, y = x] = [];     // x=1; y=1
let [x = 1, y = x] = [2];    // x=2; y=2
let [x = 1, y = x] = [1, 2]; // x=1; y=2
let [x = y, y = 1] = [];     // ReferenceError: y is not defined

2、 对象的解构赋值

2.1 语法

解构不仅可以用于数组,还可以用于对象

let { foo, bar } = { foo: 'aaa', bar: 'bbb' };
foo // "aaa"
bar // "bbb"

对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。

如果解构失败,变量的值等于undefined。

  • 对象的解构赋值,可以很方便地将现有对象的方法,赋值到某个变量。

    // 将Math对象的对数、正弦、余弦三个方法,赋值到对应的变量上
    let { log, sin, cos } = Math;
    // 将console.log赋值到log变量。
    const { log } = console;
    log('hello') // hello
    

复杂的对象结构赋值:

const node = {
  loc: {
    start: {
      line: 1,
      column: 5
    }
  }
};
let { loc, loc: { start }, loc: { start: { line }} } = node;
line // 1
loc  // Object {start: Object}
start // Object {line: 1, column: 5}

数组一样,解构也可以用于嵌套结构的对象。

2.2 默认值

对象的解构也可以指定默认值。

var {x = 3} = {};
x // 3
var {x, y = 5} = {x: 1};
x // 1
y // 5
var {x: y = 3} = {};
y // 3
var {x: y = 3} = {x: 5};
y // 5
var { message: msg = 'Something went wrong' } = {};
msg // "Something went wrong"

默认值生效的条件是,对象的属性值严格等于undefined

var {x = 3} = {x: undefined};
x // 3
var {x = 3} = {x: null};
x // null

3、 字符串的解构赋值

字符串也可以解构赋值。这是因为此时,字符串被转换成了一个类似数组的对象。

const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"

类似数组的对象都有一个length属性,因此还可以对这个属性解构赋值。

let {length : len} = 'hello';
len // 5

4、 函数参数的解构赋值

函数参数也可以使用解构赋值

function add([x, y]){  
    return x + y;
}

add([1, 2]);  // 3

上面代码中,函数add的参数表面上是一个数组,但在传入参数的那一刻,数组参数就被解构成变量xy。对于函数内部的代码来说,它们能感受到的参数就是xy

下面是另一个例子。

[[1, 2], [3, 4]].map(([a, b]) => a + b);  // [ 3, 7 ]

函数参数解构也可以使用默认值

function move({x = 0, y = 0} = {}) 
{  
    return [x, y];
}

move({x: 3, y: 8}); // [3, 8]move({x: 3}); // [3, 0]move({}); // [0, 0]move(); // [0, 0]

5、 用途

  1. 交换变量的值

    let x = 1;
    let y = 2;
    [x, y] = [y, x];
    

    上面代码交换变量xy的值,这样的写法不仅简洁,而且易读,语义非常清晰。

  2. 从函数返回多个值

    // 返回一个数组
    function example() {
      return [1, 2, 3];
    }
    let [a, b, c] = example();
    
    // 返回一个对象
    function example() {
      return {
        foo: 1,
        bar: 2
      };
    }
    let { foo, bar } = example();
    

    函数只能返回一个值,如果要返回多个值,只能将它们放在数组或对象里返回。有了解构赋值,取出这些值就非常方便。

  3. 函数参数的定义

    // 参数是一组有次序的值
    function f([x, y, z]) { ... }
    f([1, 2, 3]);
                           
    // 参数是一组无次序的值
    function f({x, y, z}) { ... }
    f({z: 3, y: 2, x: 1});
    
  4. 提取 JSON 数据

    let jsonData = {
      id: 42,
      status: "OK",
      data: [867, 5309]
    };
    
    let { id, status, data: number } = jsonData;  // data: number 约等于 number = data
    console.log(id, status, number);
    // 42, "OK", [867, 5309]
    
  5. 指定函数参数的默认值

    jQuery.ajax = function (url, {
      async = true,
      beforeSend = function () {},
      cache = true,
      complete = function () {},
      crossDomain = false,
      global = true,
      // ... more config
    } = {}) {
      // ... do stuff
    };
    
  6. 遍历 Map 结构

    const map = new Map();
    map.set('first', 'hello');
    map.set('second', 'world');
    for (let [key, value] of map) {
      console.log(key + " is " + value);
    }
    // first is hello
    // second is world
    
    // 获取键名
    for (let [key] of map) {
      // ...
    }
    // 获取键值
    for (let [,value] of map) {
      // ...
    }
    

    任何部署了 Iterator 接口的对象,都可以用for...of循环遍历。Map 结构原生支持 Iterator 接口,配合变量的解构赋值,获取键名和键值就非常方便。

  7. 输入模块的指定方法

    const { SourceMapConsumer, SourceNode } = require("source-map");
    

    加载模块时,往往需要指定输入哪些方法。解构赋值使得输入语句非常清晰。

四、 字符串扩展

1、 字符的 Unicode 表示法

ES6 加强了对 Unicode 的支持,允许采用 \uxxxx 形式表示一个字符,其中 xxxx 表示字符的 Unicode 码点。

"\u0061"
// "a"

但是,这种表示法只限于码点在 \u0000 ~ \uFFFF 之间的字符。超出这个范围的字符,必须用两个双字节的形式表示。

"\uD842\uDFB7"
// "????"
"\u20BB7"
// " 7"

上面代码表示,如果直接在 \u 后面跟上超过 0xFFFF 的数值(比如 \u20BB7 ),JavaScript 会理解成 \u20BB+7 。由于 \u20BB 是一个不可打印字符,所以只会显示一个空格,后面跟着一个 7 。

ES6 对这一点做出了改进,只要将码点放入大括号,就能正确解读该字符。

"\u{20BB7}"
// "????"
"\u{41}\u{42}\u{43}"
// "ABC"
let hello = 123;

hell\u{6F} // 123
'\u{1F680}' === '\uD83D\uDE80'
// true

js 中表示字符串的方法:

'\z' === 'z'  // true
'\172' === 'z' // true
'\x7A' === 'z' // true
'\u007A' === 'z' // true
'\u{7A}' === 'z' // true

2、 字符串的遍历器接口

ES6 为字符串添加了遍历器接口,使得字符串可以被 for…of 循环遍历。

for (let codePoint of 'foo') {
  console.log(codePoint);
}

// "f"
// "o"
// "o"

3、 模板字符串

传统的 JavaScript 语言,输出模板通常是这样写的(下面使用了 jQuery 的方法)。

$('#result').append(
  'There are <b>' + basket.count + '</b> ' +
  'items in your basket, ' +
  '<em>' + basket.onSale +
  '</em> are on sale!'
);

上面这种写法相当繁琐不方便,ES6 引入了模板字符串解决这个问题。

$('#result').append(`
  There are <b>${basket.count}</b> items
   in your basket, <em>${basket.onSale}</em>
  are on sale!
`);

模板字符串(template string)是增强版的字符串,用反引号标识。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量。

// 普通字符串
`In JavaScript '\n' is a line-feed.`
// 多行字符串
`In JavaScript this is
 not legal.`
console.log(`string text line 1
string text line 2`);
// 字符串中嵌入变量
let name = "Bob", time = "today";
`Hello ${name}, how are you ${time}?`  

上面代码中的模板字符串,都是用反引号表示。如果在模板字符串中需要使用反引号,则前面要用反斜杠转义。

var greeting = `\`Yo\` World!`;

如果使用模板字符串表示多行字符串,所有的空格和缩进都会被保留在输出之中。

$('#list').html(`
<ul>
  <li>first</li>
  <li>second</li>
</ul>
`);

上面代码中,所有模板字符串的空格和换行,都是被保留的,比如 标签前面会有一个换行。如果你不想要这个换行,可以使用 trim 方法消除它。

$('#list').html(`
<ul>
  <li>first</li>
  <li>second</li>
</ul>
`.trim());

大括号内部可以放入任意的 JavaScript 表达式,可以进行运算,以及引用对象属性。

var x = 1;
var y = 2;
`${x} + ${y} = ${x + y}`
// "1 + 2 = 3"
`${x} + ${y * 2} = ${x + y * 2}`
// "1 + 4 = 5"
var obj = {x: 1, y: 2};
`${obj.x + obj.y}`
// 3

// 内部还可以调用函数
function fn() {
  return "Hello World";
}
`foo ${fn()} bar`
// foo Hello World bar

如果大括号中的值不是字符串,将按照一般的规则转为字符串。比如,大括号中是一个对象,将默认调用对象的 toString 方法。

嵌套模板字符串

const tmpl = addrs => `
  <table>
  ${addrs.map(addr => `
    <tr><td>${addr.first}</td></tr>
    <tr><td>${addr.last}</td></tr>
  `).join('')}
  </table>
`;
const data = [
    { first: '<Jane>', last: 'Bond' },
    { first: 'Lars', last: '<Croft>' },
];
console.log(tmpl(data));

4、 标签模板

模板字符串的功能,不仅仅是上面这些。它可以紧跟在一个函数名后面,该函数将被调用来处理这个模板字符串。这被称为“标签模板”功能(tagged template)。

alert`123`

// 等同于
alert(123)

标签模板其实不是模板,而是函数调用的一种特殊形式。“标签”指的就是函数,紧跟在后面的模板字符串就是它的参数。

let a = 2;
console.log`123 ${a} asd`;

但是,如果模板字符里面有变量,就不是简单的调用了,而是会将模板字符串先处理成多个参数,再调用函数。

var a = 5;
var b = 10;
tag`Hello ${ a + b } world ${ a * b }`;
// 等同于
tag(['Hello ', ' world ', ''], 15, 50);

function tag(stringArr, value1, value2){
  // ...
}
// 等同于
function tag(stringArr, ...values){
  // ...
}

如:

let total = 30;
let msg = passthru`The total is ${total} (${total*1.05} with tax)`;
function passthru(literals) {  // 除了使用默认的 arguments 来接收不定长参数 也可以使用 ...values 来接收不定长参数
    let result = '';
    let i = 0;
    console.log(literals)  // 其为一个数组
    while (i < literals.length) {
        result += literals[i++];
        if (i < arguments.length) {  
            result += arguments[i];
        }
    }
    return result;
}
console.log(msg) // "The total is 30 (31.5 with tax)"

5、 新增方法

传统上,JavaScript 只有 indexOf 方法,可以用来确定一个字符串是否包含在另一个字符串中。ES6 又提供了三种新方法。

  • includes():返回布尔值,表示是否找到了参数字符串。
  • startsWith():返回布尔值,表示参数字符串是否在原字符串的头部。
  • endsWith():返回布尔值,表示参数字符串是否在原字符串的尾部。
let s = 'Hello world!';
s.startsWith('Hello') // true
s.endsWith('!') // true
s.includes('o') // true

如果使用第二个参数 n 时, endsWith 的行为与其他两个方法有所不同。它针对前 n 个字符,而其他两个方法针对从第 n 个位置直到字符串结束。

实例方法:repeat()

  • repeat 方法返回一个新字符串,表示将原字符串重复 n 次。

    'x'.repeat(3) // "xxx"
    'hello'.repeat(2) // "hellohello"
    'na'.repeat(0) // ""
    

实例方法:padStart()padEnd()

ES2017 引入了字符串补全长度的功能。如果某个字符串不够指定长度,会在头部或尾部补全。 padStart() 用于头部补全, padEnd() 用于尾部补全。

'x'.padStart(5, 'ab') // 'ababx'
'x'.padStart(4, 'ab') // 'abax'
'x'.padEnd(5, 'ab') // 'xabab'
'x'.padEnd(4, 'ab') // 'xaba'

如果原字符串的长度,等于或大于最大长度,则字符串补全不生效,返回原字符串。

如果省略第二个参数,默认使用空格补全长度。

实例方法:trimStart()trimEnd()

ES2019 对字符串实例新增了 trimStart()trimEnd() 这两个方法。它们的行为与trim() 一致, trimStart()消除字符串头部的空格,trimEnd() 消除尾部的空格。它们返回的都是新字符串,不会修改原始字符串。

const s = '  abc  ';
s.trim() // "abc"
s.trimStart() // "abc  "
s.trimEnd() // "  abc"

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/196860.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

TCP协议面试灵魂12 问(二)

为什么不是两次&#xff1f; 根本原因: 无法确认客户端的接收能力。 分析如下: 如果是两次&#xff0c;你现在发了 SYN 报文想握手&#xff0c;但是这个包滞留在了当前的网络中迟迟没有到达&#xff0c;TCP 以为这是丢了包&#xff0c;于是重传&#xff0c;两次握手建立好了…

机器视觉高速发展催热人工智能市场,深眸科技深度布局把握新机遇

曾经&#xff0c;冰箱侧身的标签、空调背面不显眼的小螺丝、微波炉角落里的型号编码等质量检测&#xff0c;是工业生产线中最费人工、最难检测的“老大难”。这主要是因为我国家电行业长期以混产为主要生产方式&#xff0c;一条生产线上可能有几十种型号的钣金件产品同时经受质…

文档存储Elasticsearch系列--2 ES内部原理

前言&#xff1a;ES作为nosql 的数据存储&#xff0c;为什么它在承载PB级别的数据的同时&#xff0c;又可以对外提高近实时的高效搜索&#xff0c;它又是通过什么算法完成对文档的相关性分析&#xff1b;又是怎么保证聚合的高效性&#xff1b; 1 ES 分布式文档存储&#xff1a…

人工智能导论——谓词公式化为子句集详细步骤

在谓词逻辑中&#xff0c;有下述定义&#xff1a; 原子&#xff08;atom&#xff09;谓词公式是一个不能再分解的命题。 原子谓词公式及其否定&#xff0c;统称为文字&#xff08;literal&#xff09;。PPP称为正文字&#xff0c;P\neg PP称为负文字。PPP与P\neg PP为互补文字。…

MySQL实战作业示例:从离线文件生成数据库

前言 MySQL实战的课后作业&#xff0c;作业内容具体见 https://bbs.csdn.net/topics/611904749 截至时间是 2023年2月2日&#xff0c;按时提交的同学有一位。确实这次的作业非常有挑战性&#xff0c;作业用到的内容没有百分之百的学过&#xff0c;需要大家进行深入而有效的搜索…

【MyBatis】高级映射多对一,一对多和延迟加载

数据库准备:1. 多对一:多个学生对应一个班级(学生表是主表, 班级表是副表)多种实现方式, 常见的包括三种第一种方式&#xff1a; 一条sql语句, 级联属性映射// StudentMapper.xml // 一条sql语句, 级联属性映射 <resultMap id"studentResultMap" type"Studen…

Java当中的AQS

一、什么是AQS AQS的全称是:AbstractQueuedSynchronizer AQS是java当中的一个抽象类&#xff0c;用来构建锁和同步器。 例如我们常见的ReentrantLock&#xff0c;Semaphore等等都是通过AQS来构建的。 AQS的原理 如果被请求的共享资源没有被占用&#xff0c;那么就把请求资源…

spring boot集成xxl job

目录 1.xxl job介绍 2.搭建说明 (1)配置调度中心 (2)配置执行器 (3).执行 1.xxl job介绍 官网地址:分布式任务调度平台XXL-JOB XXL-JOB是一个分布式任务调度平台&#xff0c;其核心设计目标是开发迅速、学习简单、轻量级、易扩展。 2.搭建说明 环境搭建主要分为两个部分…

《深入浅出计算机组成原理》学习笔记 Day19

冒险和预测&#xff08;三&#xff09;乱序执行参考乱序执行 尽管代码生成的指令是顺序的&#xff0c;但是如果后面的指令和前面的指令独立&#xff0c;完全不需要等待前面的指令运算完成&#xff0c;可以先执行。 这种解决方案称为乱序执行&#xff08;Out-of-Order Executi…

程序加载与运行过程中的资源分配与管理

目录 程序的加载 程序的内存空间 程序入口地址 BSS段初始化 程序运行过程中的堆栈管理 栈内存管理 变量的作用域&#xff1a; 栈溢出攻击原理 Linux堆内存管理 查看进程内存布局 内存分配器 内存块合并 top chunk 程序的运行分两种情况&#xff1a;一种是在有操作…

矩阵理论复习(九)

A为正规矩阵时&#xff0c;A的奇异值是A的特征值的模。A为半正定Hermite矩阵时&#xff0c;A的奇异值是A的特征值。 最佳逼近解 最小二乘解 矩阵的单边逆 A是左可逆的充要条件是A为列满秩矩阵 A是左可逆的充要条件是NA{0} 投影矩阵N(A)R(I-A),N(I-A)R(A) A是右可逆的充要…

腾讯电子签小程序跳转(app 跳小程序,小程序跳小程序) Api

腾讯电子签 官网地址&#xff1a;腾讯电子签跳转 api 文档 let id‘yDw9jUUgyg34gq97U7WZ9b1rWEBV******’ let name ‘张**’ let phone ‘MTQ3NDU3Oidioidkl’ let path pages/guide?fromSFY&toMVP_CONTRACT_COVER&id${id}&name${name}&phone${phone} wx…

Echarts解决左右上下边距问题( 两种方法)

第007个点击查看专栏目录文章目录示例效果示例源代码&#xff08;共88行&#xff09;相关资料参考相应的设置参数&#xff08;方法1&#xff09;相应的设置参数&#xff08;方法2&#xff09;专栏介绍示例效果 没有添加grid之前&#xff08;有grid的默认值来控制&#xff09; …

Web 3 财富分配方式

文章作者&#xff1a;Andrew Beal每个星期四&#xff0c;Forta 基金会团队都会在 Zoom 上聚会&#xff0c;享受虚拟欢乐时光。我还没有亲自见过我的一些同事&#xff0c;所以这是我唯一一次了解他们在办公室之外的身份。每个人都有故事要讲&#xff0c;你只需要问。规则是 “我…

Canal安装和配置

Canal安装和配置1.开启MySQL主从1.1.开启binlog1.2.设置用户权限2.安装Canal2.1.创建网络2.2.安装Canal2.3.查看canal是否与mysql建立连接下面我们就开启mysql的主从同步机制&#xff0c;让Canal来模拟salve 1.开启MySQL主从 Canal是基于MySQL的主从同步功能&#xff0c;因此…

wamp内置mysql和学习后端下载mysql相冲突问题

文章目录前言1.将后端的mysql放入wamp路径下2.打包代码文件3&#xff0c;查询wamp集合环境换mysql的办法成功way前言 尝试了各种能够兼容两者的办法都失败了 所以一气之下把wamp内的mysql删了&#xff0c;使前后端都使用一个mysql 1.将后端的mysql放入wamp路径下 将后端的mysq…

【6s965-fall2022】量化 Quantization Ⅱ

什么是线性量化 rS(q−Z)r S(q - Z)rS(q−Z) 式中&#xff0c;SSS是比例因子&#xff0c;通常是一个浮点数&#xff1b;qqq是rrr的量化后的表示&#xff0c;是一个整数&#xff1b;ZZZ也是一个整数&#xff0c;把qqq中和ZZZ相同的整数映射到rrr中零&#xff0c;因此ZZZ是零点偏…

24_mimikatz

mimikatz 一、介绍 二、修改注册表抓取明文密码 当目标为win10或2012R2以上时&#xff0c;默认在内存缓存中禁止保存明文密码&#xff0c;但可以通过修改注册表的方式抓取明文。 重启或用户重新登录后可以成功抓取 reg add HKLM\SYSTEM\CurrentControlSet\Control\Security…

Java设计模式总结

java常用七种设计模式总结单例模式单例模式的实现第 1 种&#xff1a;懒汉式单例第 2 种&#xff1a;饿汉式单例工厂方法模式一&#xff0c;简单工厂模式二、工厂方法模式三、抽象工厂模式建造者模式策略模式模板方法责任链模式代理模式适配器模式观察者模式单例模式 单例模式…

第三届腾讯Light技术公益创造营启动

简介 腾讯Light技术公益创造营通过持续探索技术与公益的结合&#xff0c;已经打造出了包括推进公众参与中华白海豚保育的公益计划、使用AI技术助力新生儿黄疸诊断的“高危儿拯救计划”等一批优秀项目 全面升级的第三届腾讯Light技术公益创造营在海南海口正式启动&#xff0c;…