ES6学习

本文介绍了ES6中的let和const声明变量的规则,包括块级作用域和暂时性死区。接着讨论了箭头函数、模板字符串、数组和对象的扩展,如扩展运算符、Promise对象用于异步编程,以及Map和Set数据结构。此外,还提到了for...in和for...of循环,以及模块导入导出机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

ECMAScript6 入门(阮一峰)https://es6.ruanyifeng.com/#docs/

let |const命令

let

解决:var作用域问题
1、局部变量,声明的变量只在所在的代码块内有效

 if(true){
        var a="1"
        let b='2'
    }
    console.log(a)
    console.log(b)
    </script>

在这里插入图片描述
for 循环还有一个特别之处,就是设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域

{
  let a = 10;
  var b = 1;
}

a // ReferenceError: a is not defined.
b // 1

2、暂时性死区
ES6 明确规定,如果区块中存在let和const命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。
总之,在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)。

    console.log(a)
    console.log(b)
    var a=1
    let b=2

在这里插入图片描述

4、不允许重复声明
在这里插入图片描述

注:var 没有块(if、for)作用域所引起的后果:

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title></title>
	</head>
	<body>
		<button>btn</button>
		<button>btn</button>
		<button>btn</button>
		<button>btn</button>
		<button>btn</button>
	</body>
	<script>
		
		var btn=document.getElementsByTagName("button")
		for (var i = 0; i < 4; i++) {
			btn[i].addEventListener('click',function(){
				console.log("第"+i+"个按钮")
			})
		}
	</script>
</html>

结果:
点击任何一个按钮log都是:“第4个按钮”
按理来说不应该是点击第几个就第几个么,结果全部都是“4”;那是因为循环完后,i=4,打印时,i只能是4.

let实际上为 JavaScript 新增了块级作用域。

块级作用域的出现,实际上使得获得广泛应用的匿名立即执行函数表达式(匿名 IIFE)不再必要了

(function(){
}());

const

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

const foo = {};

// 为 foo 添加一个属性,可以成功
foo.prop = 123;
foo.prop // 123

// 将 foo 指向另一个对象,就会报错
foo = {}; // TypeError: "foo" is read-only

上面代码中,常量foo储存的是一个地址,这个地址指向一个对象。不可变的只是这个地址,即不能把foo指向另一个地址,但对象本身是可变的,所以依然可以为其添加新属性。

1、声明一个只读的常量只有在所在的代码块内有效
2、不存在变量提升
3、声明之前变量都不可用,称为暂时性死区
4、不允许重复声明

顶层对象的属性

顶层对象,在浏览器环境指的是window对象
顶层对象的属性与全局变量挂钩,被认为是 JavaScript 语言最大的设计败笔之一。这样的设计带来了几个很大的问题,首先是没法在编译时就报出变量未声明的错误,只有运行时才能知道(因为全局变量可能是顶层对象的属性创造的,而属性的创造是动态的);其次,程序员很容易不知不觉地就创建了全局变量(比如打字出错);最后,顶层对象的属性是到处可以读写的,这非常不利于模块化编程。另一方面,window对象有实体含义,指的是浏览器的窗口对象,顶层对象是一个有实体含义的对象,也是不合适的。

ES6 为了改变这一点,一方面规定,为了保持兼容性,var命令和function命令声明的全局变量,依旧是顶层对象的属性;另一方面规定,let命令、const命令、class命令声明的全局变量,不属于顶层对象的属性。
也就是说,从 ES6 开始,全局变量将逐步与顶层对象的属性脱钩。

变量的结构赋值

批量赋值
只要某种数据结构具有 Iterator 接口,都可以采用数组形式的解构赋值
ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构
let {name}=obj
以前:

数组

数组的元素是按次序排列的,变量的取值由它的位置决定;

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

现在:

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 // []

如果解构不成功,变量的值就等于undefined。

嵌套
let obj = {
  p: [
    'Hello',
    { y: 'World' }
  ]
};

let { p: [x, { y }] } = obj;
x // "Hello"
y // "World"

对象

而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。
对象的解构赋值用于从一个对象取值,相当于将目标对象自身的所有可遍历的(enumerable)、但尚未被读取的属性,分配到指定的对象上面。所有的键和它们的值,都会拷贝到新对象上面
等号右边是个对象

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

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

let o={}
	o.aa="A"
	o.bb="B"
	let {aa,bb}=o
	console.log(aa)
默认值
let [foo = true] = [];
foo // true

let [x, y = 'b'] = ['a']; // x='a', y='b'
let [x, y = 'b'] = ['a', undefined]; // x='a', y='b'
默认值
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"
嵌套
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}
对象名和属性名不一致
let { foo: baz } = { foo: 'aaa', bar: 'bbb' };
baz // "aaa"

let obj = { first: 'hello', last: 'world' };
let { first: f, last: l } = obj;
f // 'hello'
l // 'world'

用途

  • 交换变量的值
let x = 1;
let y = 2;

[x, y] = [y, x];
  • 从函数返回多个值
/ 返回一个数组

function example() {
  return [1, 2, 3];
}
let [a, b, c] = example();

// 返回一个对象

function example() {
  return {
    foo: 1,
    bar: 2
  };
}
let { foo, bar } = example();
  • 函数参数的定义
// 参数是一组有次序的值
function f([x, y, z]) { ... }
f([1, 2, 3]);

// 参数是一组无次序的值
function f({x, y, z}) { ... }
f({z: 3, y: 2, x: 1});
  • 提取 JSON 数据
    解构赋值对提取 JSON 对象中的数据,尤其有用。
let jsonData = {
  id: 42,
  status: "OK",
  data: [867, 5309]
};

let { id, status, data: number } = jsonData;

console.log(id, status, number);
  • 遍历 Map 结构
    任何部署了 Iterator 接口的对象,都可以用for…of循环遍历。Map 结构原生支持 Iterator 接口,配合变量的解构赋值,获取键名和键值就非常方便。
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) {
  // ...
}
  • 输入模块的指定方法
    加载模块时,往往需要指定输入哪些方法。解构赋值使得输入语句非常清晰。
const { SourceMapConsumer, SourceNode } = require("source-map");

模板字符串

字符串的一种新的表现形式,其中的空格、缩进、换行都会被保留。
解决:‘~~’+变量+‘~’+变量 。这种形式的字符串拼接
` `
模板字符串拼接的改进
${}:大括号的可以进行运算,也可以引用变量、函数

// 变量
  let name="阿萨德"
       const  box= document.querySelector(".box")
        let str=`<ul><li id="1">${name}</li></ul>`
        box.innerHTML=str
// 函数
	function fstr(){
		return "world"
	}
	let str=`hello ${fstr()}`

字符串新增方法

1、String.raw()–返回一个斜杠不会被转义的字符串,常用语模板字符串。
传统上,JavaScript 只有indexOf方法,可以用来确定一个字符串是否包含在另一个字符串中。ES6 又提供了三种新方法。

  • includes()–返回boolean 是否包含字符串
  • startWith()–返回boolean 是否在头部
  • endWith()–返回boolean 是否在尾部
    3、repat()–返回一个重复N次的新字符串
    4、padStart()–头部补全
    padEnd()–尾部补全
    5、它们的行为与trim()一致,
    trimStart()–头部去空格
    trimEnd()–尾部去空格
    6、 replaceAll()-- 在replace()进化为替换全部
    7、for…of可遍历字符串

函数扩展

ES6之前,只能通过在调用函数时设置默认值

function test(str){
}
test('hello')

函数的参数设置默认值,即直接写在参数定义的后面。

function log(x, y = 'World') {
  console.log(x, y);
}

log('Hello') // Hello World
log('Hello', 'China') // Hello China
log('Hello', '') // Hello

与解构赋值默认值结合使用

function foo({x, y = 5}) {
  console.log(x, y);
}

foo({}) // undefined 5
foo({x: 1}) // 1 5
foo({x: 1, y: 2}) // 1 2
foo() // TypeError: Cannot read property 'x' of undefined

rest参数

箭头函数中无法使用arguments,reset参数则来解决此问题
ES6 引入 rest 参数(形式为…变量名)用于获取函数的多余参数,这样就不需要使用arguments对象了
rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。
rest 参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。
funcation(…args)
args即rest参数,其为一个数组。

function test(a,b,...rest){
	console.log(a)
	console.log(b)
	console.log(rest)
}
test(1,2,3,4,5,6)
a:1
b:2
rest:[4,5,6]

箭头函数

function(){}------->()=>{}
语法格式:
无参匿名函数()=>{}
只有一个参数的匿名函数,有括号或无括号(x)=>{}或者x=>{}
如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回。

var f = () => 5;
// 等同于
var f = function () { return 5 };

var sum = (num1, num2) => num1 + num2;
// 等同于
var sum = function(num1, num2) {
  return num1 + num2;
};
	let a =(x,y)=>{
			if(x>10){
				return x+y
			}else{
				return y
			}
		}
		console.log(a(5,2))

数组的扩展

…运算符

console.log(…[1,2,3])-----1 2 3
完全相当于reset的逆运算,将一个数组转为用逗号分隔的参数序列

	console.log(...[1, 2, 3])
// 1 2 3

console.log(1, ...[2, 3, 4], 5)
// 1 2 3 4 5
复制数组
const a1 = [1, 2];
// 写法一
const a2 = [...a1];
// 写法二
const [...a2] = a1;
合并数组
const arr1 = ['a', 'b'];
const arr2 = ['c'];
const arr3 = ['d', 'e'];

// ES5 的合并数组
arr1.concat(arr2, arr3);
// [ 'a', 'b', 'c', 'd', 'e' ]

// ES6 的合并数组
[...arr1, ...arr2, ...arr3]
// [ 'a', 'b', 'c', 'd', 'e' ]
解构赋值相结合

如果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错。

const [first, ...rest] = [1, 2, 3, 4, 5];
first // 1
rest  // [2, 3, 4, 5]

const [first, ...rest] = [];
first // undefined
rest  // []

const [first, ...rest] = ["foo"];
first  // "foo"
rest   // []

对象的扩展

属性的简洁表示法

const foo = 'bar';
const baz = {foo};
baz // {foo: "bar"}

// 等同于
const baz = {foo: foo};

上面代码中,变量foo直接写在大括号里面。这时,属性名就是变量名, 属性值就是变量值。

方法简写

const o = {
  method() {
    return "Hello!";
  }
};

// 等同于

const o = {
  method: function() {
    return "Hello!";
  }
};

综合

let birth = '2000/01/01';

const Person = {

  name: '张三',

  //等同于birth: birth
  birth,

  // 等同于hello: function ()...
  hello() { console.log('我的名字是', this.name); }

};

注意,简写的对象方法不能用作构造函数

属性名表达式

JavaScript 定义对象的属性,有两种方法。

// 方法一
obj.foo = true;

// 方法二
obj['a' + 'bc'] = 123;

属性遍历

ES6 一共有 5 种方法可以遍历对象的属性。
(1)for…in
for…in循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性)。
(2)Object.keys(obj)
Object.keys返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)的键名。
(3)Object.getOwnPropertyNames(obj)
Object.getOwnPropertyNames返回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)的键名。
(4)Object.getOwnPropertySymbols(obj)
Object.getOwnPropertySymbols返回一个数组,包含对象自身的所有 Symbol 属性的键名。
(5)Reflect.ownKeys(obj)
Reflect.ownKeys返回一个数组,包含对象自身的(不含继承的)所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举。
以上的 5 种方法遍历对象的键名,都遵守同样的属性遍历的次序规则。
首先遍历所有数值键,按照数值升序排列。
其次遍历所有字符串键,按照加入时间升序排列。
最后遍历所有 Symbol 键,按照加入时间升序排列。

解构赋值

对象的扩展运算符

let car={price:‘199’}
// 克隆一个和car一样的对象
let newCar={…car}
// 有相同属相名覆盖,新增属性
let newCar={…car,age:‘11’}

对象方法扩展

ES6
let obj={
run(){
}
}

ES5
var obj={
run:function(){
}
}

对象的扩展运算符(…)

复制、合并==浅拷贝(对原变量的引用而并非生成新的)

用于取出参数对象的所有可遍历属性,拷贝到当前对象之中。等同于Object.assign()

	let person={
			name:"adas",
			age:'111',
			lv:222
		}
		const aa={
			...person
		}
		console.log(aa)

let ab={...p1,...p2}
// 同名属性会被覆盖

如果源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用

	var persson1={
			name:"person1",
			age:111,
			lv:111
		}
		var persson2={
			name:"person2",
			age:22,
			aasd:{
				name:"asa"
			}
		}
		var a={
			...persson1,
			...persson2
		}
		Object.assign(persson1,persson2)
		console.log(persson1)
		persson2.aasd.name="阿萨德"
		console.log(persson1)

在这里插入图片描述

Object.keys(),

var obj = { foo: 'bar', baz: 42 };
Object.keys(obj)
// ["foo", "baz"]

Object.values()

const obj = { foo: 'bar', baz: 42 };
Object.values(obj)
// ["bar", 42]

Object.entries()

方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值对数组。

const obj = { foo: 'bar', baz: 42 };
Object.entries(obj)
// [ ["foo", "bar"], ["baz", 42] ]

另一个用处是,将对象转为真正的Map结构

const obj = { foo: 'bar', baz: 42 };
const map = new Map(Object.entries(obj));
map // Map { foo: "bar", baz: 42 }

Object.fromEntries()

Object.entries()的逆操作,用于将一个键值对数组转为对象。

Object.fromEntries([
  ['foo', 'bar'],
  ['baz', 42]
])
// { foo: "bar", baz: 42 }

该方法的主要目的,是将键值对的数据结构还原为对象,因此特别适合将 Map 结构转为对象。

// 例一
const entries = new Map([
  ['foo', 'bar'],
  ['baz', 42]
]);

Object.fromEntries(entries)
// { foo: "bar", baz: 42 }

// 例二
const map = new Map().set('foo', true).set('bar', false);
Object.fromEntries(map)
// { foo: true, bar: false }

配合URLSearchParams对象,将查询字符串转为对象
适合参数取值
Object.fromEntries(new URLSearchParams(‘foo=bar&baz=qux’))
// { foo: “bar”, baz: “qux” }

运算符的扩展

链判断运算符

读取对象内部的某个属性,往往需要判断一下,属性的上层对象是否存在。比如,读取message.body.user.firstName这个属性,安全的写法是写成下面这样。

// 错误的写法
const  firstName = message.body.user.firstName || 'default';

// 正确的写法
const firstName = (message
  && message.body
  && message.body.user
  && message.body.user.firstName) || 'default';

三目运算符?: 也常用于判断对象是否存在

const fooValue = fooInput ? fooInput.value : undefined

这样的层层判断非常麻烦,因此 ES2020 引入了“链判断运算符”(optional chaining operator)简化写法:?.,

const firstName = message?.body?.user?.firstName || 'default';

面代码使用了?.运算符,直接在链式调用的时候判断,左侧的对象是否为null或undefined。如果是的,就不再往下运算,而是返回undefined。

Null 判断运算符

如果某个属性的值是null或undefined,有时候需要为它们指定默认值。常见做法是通过||运算符指定默认值。
只要属性的值为null或undefined,默认值就会生效,但是属性的值如果为空字符串或false或0,默认值也会生效。
为了避免这种情况,ES2020 引入了一个新的 Null 判断运算符??。它的行为类似||,但是只有运算符左侧的值为null或undefined时,才会返回右侧的值。
这个运算符的一个目的,就是跟链判断运算符?.配合使用,为null或undefined的值设置默认值

const headerText = response.settings.headerText ?? 'Hello, world!';
const animationDuration = response.settings.animationDuration ?? 300;
const showSplashScreen = response.settings.showSplashScreen ?? true;

逻辑赋值运算符

将逻辑运算符与赋值运算符进行结合。

x ||= y
// 等同于
x || (x = y)

// 与赋值运算符
x &&= y
// 等同于
x && (x = y)

// Null 赋值运算符
x ??= y
// 等同于
x ?? (x = y)

相当于先进行逻辑运算,然后根据运算结果,再视情况进行赋值运算。

Set和Map

Set

它类似于数组,但是成员的值都是唯一的,没有重复的值。
Set 内部判断两个值是否不同,使用的算法叫做“Same-value-zero equality”,它类似于精确相等运算符(===),
向 Set 加入值的时候,不会发生类型转换

const set = new Set([1, 2, 3, 4, 4]);
属性

Set 结构的实例有以下属性。

  • Set.prototype.constructor:构造函数,默认就是Set函数。
  • Set.prototype.size:返回Set实例的成员总数。
方法
  • Set.prototype.add(value):添加某个值,返回 Set 结构本身。
  • Set.prototype.delete(value):删除某个值,返回一个布尔值,表示删除是否成功。
  • Set.prototype.has(value):返回一个布尔值,表示该值是否为Set的成员。
  • Set.prototype.clear():清除所有成员,没有返回值。
s.add(1).add(2).add(2);
// 注意2被加入了两次

s.size // 2

s.has(1) // true
s.has(2) // true
s.has(3) // false

s.delete(2) // true
s.has(2) // false

Array.from()方法可以将 Set 结构转为数组。

const items = new Set([1, 2, 3, 4, 5]);
const array = Array.from(items);
遍历

Set 结构的实例有四个遍历方法,可以用于遍历成员。

Set.prototype.keys():返回键名的遍历器
Set.prototype.values():返回键值的遍历器
key与value相同
Set.prototype.entries():返回键值对的遍历器
Set.prototype.forEach():使用回调函数遍历每个成员

Map

JavaScript 的对象(Object),本质上是键值对的集合(Hash 结构),但是传统上只能用字符串当作键。这给它的使用带来了很大的限制。
为了解决这个问题,ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object 结构提供了“字符串—值”的对应,Map 结构提供了“值—值”的对应,是一种更完善的 Hash 结构实现。如果你需要“键值对”的数据结构,Map 比 Object 更合适

const m = new Map();
const o = {p: 'Hello World'};

m.set(o, 'content')
m.get(o) // "content"

m.has(o) // true
m.delete(o) // true
m.has(o) // false
属性
  • size
    size属性返回 Map 结构的成员总数。
方法
  • set(key, value)
    set方法设置键名key对应的键值为value,然后返回整个 Map 结构。如果key已经有值,则键值会被更新,否则就新生成该键。
    get(key)
  • get方法读取key对应的键值,如果找不到key,返回undefined。
  • has(key)
    has方法返回一个布尔值,表示某个键是否在当前 Map 对象之中。
  • delete(key)
    delete()方法删除某个键,返回true。如果删除失败,返回false。
  • clear()
    clear()方法清除所有成员,没有返回值。
遍历

Map.prototype.keys():返回键名的遍历器。
Map.prototype.values():返回键值的遍历器。
Map.prototype.entries():返回所有成员的遍历器。
Map.prototype.forEach():遍历 Map 的所有成员。

Promise对象

异步编程的一种解决方案,比传统的解决方案(回调函数和事件)更加的合理;简单来说,就是一个容器,保存着某个未来才会结束的事件。
语法上来说,就是一个对象,可以用来获取异步操作的
其三种状态:未完成、成功、失败。三种状态不可逆
new Promise(成功函数、失败函数)=>{
.ajax({
})
}
// TODO

.then()
.catch()

Iterator 和 for…of 循环

Iterator(遍历器)的概念

JavaScript 原有的表示“集合”的数据结构,主要是数组(Array)和对象(Object),ES6 又添加了Map和Set。这样就有了四种数据集合,用户还可以组合使用它们,定义自己的数据结构,比如数组的成员是Map,Map的成员是对象。这样就需要一种统一的接口机制,来处理所有不同的数据结构。
遍历器(Iterator)就是这样一种机制。
Iterator 接口的目的,就是为所有数据结构,提供了一种统一的访问机制,即for…of循环(详见下文)。当使用for…of循环遍历某种数据结构时,该循环会自动去寻找 Iterator 接口
ES6 规定,默认的 Iterator 接口部署在数据结构的Symbol.iterator属性,或者说,一个数据结构只要具有Symbol.iterator属性,就可以认为是“可遍历的”(iterable)

原生具备 Iterator 接口的数据结构如下。

  • Array
  • Map
  • Set
  • String
  • TypedArray
  • 函数的 arguments 对象
  • NodeList 对象
调用 Iterator 接口的场合

有一些场合会默认调用 Iterator 接口(即Symbol.iterator方法),除了下文会介绍的for…of循环,还有几个别的场合。

  • 解构赋值
    对数组和 Set 结构进行解构赋值时,会默认调用Symbol.iterator方法。
  • 扩展运算符
    扩展运算符(…)也会调用默认的 Iterator 接口。

for…of 循环

ES6 借鉴 C++、Java、C# 和 Python 语言,引入了for…of循环,作为遍历所有数据结构的统一的方法。

一个数据结构只要部署了Symbol.iterator属性,就被视为具有 iterator 接口,就可以用for…of循环遍历它的成员。也就是说,for…of循环内部调用的是数据结构的Symbol.iterator方法。

for…of循环可以使用的范围包括数组、Set 和 Map 结构、某些类似数组的对象(比如arguments对象、DOM NodeList 对象)、后文的 Generator 对象,以及字符串。

Module

概述

历史上,JavaScript 一直没有模块(module)体系,无法将一个大程序拆分成互相依赖的小文件,再用简单的方法拼装起来。其他语言都有这项功能,比如 Ruby 的require、Python 的import,甚至就连 CSS 都有@import,但是 JavaScript 任何这方面的支持都没有,这对开发大型的、复杂的项目形成了巨大障碍。
在 ES6 之前,社区制定了一些模块加载方案,最主要的有 CommonJS 和 AMD 两种。前者用于服务器,后者用于浏览器。ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案。
ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS 和 AMD 模块,都只能在运行时确定这些东西。

ES6 模块不是对象,而是通过export命令显式指定输出的代码,再通过import命令输入。

严格模式

ES6 的模块自动采用严格模式,不管你有没有在模块头部加上"use strict";。
严格模式主要有以下限制。

  • 变量必须声明后再使用
  • 函数的参数不能有同名属性,否则报错
  • 不能使用with语句
  • 不能对只读属性赋值,否则报错
  • 不能使用前缀 0 表示八进制数,否则报错
  • 不能删除不可删除的属性,否则报错
  • 不能删除变量delete prop,会报错,只能删除属性delete global[prop]
  • eval不会在它的外层作用域引入变量
  • eval和arguments不能被重新赋值
  • arguments不会自动反映函数参数的变化
  • 不能使用arguments.callee
  • 不能使用arguments.caller
  • 禁止this指向全局对象
  • 不能使用fn.caller和fn.arguments获取函数调用的堆栈
  • 增加了保留字(比如protected、static和interface)
    上面这些限制,模块都必须遵守。由于严格模式是 ES5 引入的,不属于 ES6,所以请参阅相关 ES5 书籍,本书不再详细介绍了。

其中,尤其需要注意this的限制。ES6 模块之中,顶层的this指向undefined,即不应该在顶层代码使用this。

export 命令

一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。如果你希望外部能够读取模块内部的某个变量,就必须使用export关键字输出该变量。

指定输出
export var firstName = 'Michael';
export var lastName = 'Jackson';
export var year = 1958;

export的写法,除了像上面这样,还有另外一种:批量输出

var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;

export { firstName, lastName, year };

目前,export 命令能够对外输出的就是三种接口:函数(Functions), 类(Classes),var、let、const 声明的变量(Variables)

import命令

使用export命令定义了模块的对外接口以后,其他 JS 文件就可以通过import命令加载这个模块。
import命令接受一对大括号,里面指定要从其他模块导入的变量名。大括号里面的变量名,必须与被导入模块对外接口的名称相同

指定加载
import { firstName, lastName, year } from './profile.js';

如果想为输入的变量重新取一个名字,import命令要使用as关键字,将输入的变量重命名。

import { lastName as surname } from './profile.js';
整体模块加载

除了指定加载某个输出值,还可以使用整体加载,即用星号(*)指定一个对象,所有输出值都加载在这个对象上面。无括号

import * as circle from './circle';

export default 命令

export default命令其实只是输出一个叫做default的变量
从前面的例子可以看出,使用import命令的时候,用户需要知道所要加载的变量名或函数名,否则无法加载。但是,用户肯定希望快速上手,未必愿意阅读文档,去了解模块有哪些属性和方法。

为了给用户提供方便,让他们不用阅读文档就能加载模块,就要用到export default命令,为模块指定默认输出

// export-default.js
export default function () {
  console.log('foo');
}

上面代码是一个模块文件export-default.js,它的默认输出是一个函数。

其他模块加载该模块时,import命令可以为该匿名函数指定任意名字

// import-default.js
import customName from './export-default';
customName(); // 'foo'

上面代码的import命令,可以用任意名称指向export-default.js输出的方法,这时就不需要知道原模块输出的函数名。需要注意的是,这时import命令后面,不使用大括号

export default命令用在非匿名函数前,也是可以的。

// export-default.js
export default function foo() {
  console.log('foo');
}

// 或者写成

function foo() {
  console.log('foo');
}

export default foo;

foo函数的函数名foo,在模块外部是无效的。加载的时候,视同匿名函数加载。

导出一个包含多个方法的对象

function method1() {
    console.log("Method 1");
}

function method2() {
    console.log("Method 2");
}

function method3() {
    console.log("Method 3");
}

// 导出一个包含多个方法的对象
export default {
    method1,
    method2,
    method3
};

Module 加载

HTML 网页中,浏览器通过

加载规则

浏览器加载 ES6 模块,也使用<script>标签,但是要加入type="module"属性

<script type="module" src="./foo.js"></script>

上面代码在网页中插入一个模块foo.js,由于type属性设为module,所以浏览器知道这是一个 ES6 模块

浏览器对于带有type="module"的<script>,都是异步加载,不会造成堵塞浏览器,即等到整个页面渲染完,再执行模块脚本,等同于打开了<script>标签的defer属性。

ES6 模块与 CommonJS 模块的差异

它们有三个重大差异。

  • CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
    CommonJS 模块输出的是值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。
    ES6 模块的运行机制与 CommonJS 不一样。JS 引擎对脚本静态分析的时候,遇到模块加载命令import,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。
  • CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
    CommonJS 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。
  • CommonJS 模块的require()是同步加载模块,ES6 模块的import命令是异步加载,有一个独立的模块依赖的解析阶段。

ES6 模块的import命令可以加载 CommonJS 模块,但是只能整体加载,不能只加载单一的输出项。

JS高阶函数

filter/reduce/map

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值