(1)ES5 只有全局作用域和函数作用域,没有块级作用域,这带来很多不合理的场景
(2)ES6 的块级作用域
(3)注意点
① 浏览器的 ES6 环境中,块级作用域内声明的函数,行为类似于var
声明的变量。但是一般不写函数声明语句,而是写函数表达式。
相关规则如下:
var
,即会提升到全局作用域或函数作用域的头部。② ES6 的块级作用域必须有大括号,如果没有大括号,JavaScript 引擎就认为不存在块级作用域。
(1)只在所处于的块级有效
if (true) {
let a = 10;
}
console.log(a) // a is not defined
注意:使用let关键字声明的变量才具有块级作用域,使用var声明的变量不具备块级作用域特性。
(2)不存在变量提升
console.log(a); // a is not defined
let a = 20;
(3)暂时性死区
ES6中,在代码块内,使用let/const命令声明变量之前,该变量都是不可用的,在变量声明之前属于该变量的“死区”。(也就是变量不提升的效果)
暂时性死区的本质:只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。
var tmp = 123;
if (true) {
tmp = 'abc'; // ReferenceError
let tmp;
}
经典面试题 :
var arr = [];
for (var i = 0; i < 2; i++) {
arr[i] = function () {
console.log(i);
}
}
arr[0](); // 2
arr[1](); // 2
i
是
var
命令声明的,在全局范围内都有效,所以全局只有一个变量
i
。每一次循环,变量
i
的值都会发生改变,而循环内被赋给数组
a
的函数内部的
console.log(i)
,里面的
i
指向的就是全局的
i
。也就是说,所有数组
a
的成员里面的
i
,指向的都是同一个
i
,导致运行时输出的是最后一轮的
i
的值,也就是2。
let arr = [];
for (let i = 0; i < 2; i++) {
arr[i] = function () {
console.log(i);
}
}
arr[0](); // 0
arr[1](); // 1
i
是
let
声明的,当前的
i
只在本轮循环有效,所以每一次循环的
i
其实都是一个新的变量。你可能会问,如果每一轮循环的变量
i
都是重新声明的,那它怎么知道上一轮循环的值,从而计算出本轮循环的值?这是因为 JavaScript 引擎内部会记住上一轮循环的值,初始化本轮的变量
i
时,就在上一轮循环的基础上进行计算。
关于for循环作用域的重要补充:
设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域。
for (let i = 0; i < 3; i++) {
let i = 'abc';
console.log(i);
}
// abc
// abc
// abc
上面代码正确运行,输出了 3 次abc
。这表明函数内部的变量i
与循环变量i
不在同一个作用域,有各自单独的作用域。
(4)不允许重复声明
let
不允许在相同作用域内,重复声明同一个变量。因此,不能在函数内部重新声明参数。
const
实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。对于
简单类型的数据(数值、字符串、布尔值),
值就保存在变量指向的那个内存地址,因此等同于常量。但对于
复合类型的数据(主要是对象和数组),
变量指向的内存地址,保存的只是一个指向实际数据的指针,
const
只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了。
if (true) {
const a = 10;
}
console.log(a) // a is not defined
(2)声明常量时必须赋值(初始化)
const PI; // Missing initializer in const declaration
(3)常量赋值后,值不能修改
const PI = 3.14;
PI = 100; // Assignment to constant variable.
var存在变量提升,变量可以在声明之前使用。let、const不存在变量提升,变量一定要声明后才能使用,否则会报错。
let [a, b, c] = [1, 2, 3];
let [foo] = [];
let [bar, foo] = [1];
注:等号的右边不是数组(或者不是可遍历的结构),那么将会报错。事实上,只要某种数据结构具有 Iterator 接口,都可以采用数组形式的解构赋值。
数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。
let person = { name: 'zhangsan', age: 20 };
let { name, age } = person;
console.log(name); // 'zhangsan'
console.log(age); // 20
let {name: myName, age: myAge} = person; // myName myAge 属于别名
console.log(myName); // 'zhangsan'
console.log(myAge); // 20
将现有对象的方法,赋值到某个变量:
// 例一
let { log, sin, cos } = Math;
// 例二
const { log } = console;
log('hello') // hello
对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,而不是前者
let { foo: baz } = { foo: 'aaa', bar: 'bbb' }; //foo是匹配模式,baz才是变量
baz // "aaa"
foo // error: foo is not defined
数组和对象的解构都可以指定默认值。
默认值生效的条件是,对象的属性值严格等于undefined
。
let [foo = true] = [];
foo // true
let [x, y = 'b'] = ['a']; // x='a', y='b'
var {x = 3} = {};
x // 3
var {x, y = 5} = {x: 1};
x // 1
y // 5
var {x = 3} = {x: undefined};
x // 3
var {x = 3} = {x: null};
x // null
length
属性,因此还可以对这个属性解构赋值。const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"
let {length : len} = 'hello';
len // 5
解构赋值的规则是,只要等号右边的值不是对象或数组,就先将其转为对象。由于
undefined
和null
无法转为对象,所以对它们进行解构赋值,都会报错。
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();
let jsonData = {
id: 42,
status: "OK",
data: [867, 5309]
};
let { id, status, data: number } = jsonData;
console.log(id, status, number);
// 42, "OK", [867, 5309]
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");
() => {} //():代表是函数; =>:必须要的符号,指向哪一个代码块;{}:函数体
const fn = () => {}//代表把一个函数赋值给fn
function sum(num1, num2) {
return num1 + num2;
}
//ES6写法
const sum = (num1, num2) => num1 + num2;
如果形参只有一个,可以省略小括号
function fn (v) {
return v;
}
//ES6写法
const fn = v => v;
const obj = { name: '张三'}
function fn () {
console.log(this);//this 指向 是obj对象
return () => {
console.log(this);
//this 指向 的是箭头函数定义的位置,那么这个箭头函数定义在fn里面,而这个fn指向是的obj对象,所以这个this也指向是obj对象
}
}
const resFn = fn.call(obj);
resFn();
小结:
箭头函数中不绑定this,箭头函数中的this指向是它所定义的位置,可以简单理解成,定义箭头函数中的作用域的this指向谁,它就指向谁
- 箭头函数的优点在于解决了this执行环境所造成的一些问题。比如:解决了匿名函数this指向的问题(匿名函数的执行环境具有全局性),包括setTimeout和setInterval中使用this所造成的问题
面试题:
var age = 100;
var obj = {
age: 20,
say: () => {
alert(this.age)
}
}
obj.say();
//箭头函数this指向的是被声明的作用域里面,而对象没有作用域的,所以箭头函数虽然在对象中被定义,但是this指向的是全局作用域
function sum (first, ...args) {
console.log(first); // 10
console.log(args); // [20, 30]
}
sum(10, 20, 30)
let students = ['wangwu', 'zhangsan', 'lisi'];
let [s1, ...s2] = students;
console.log(s1); // 'wangwu'
console.log(s2); // ['zhangsan', 'lisi']
let ary = [1, 2, 3];
...ary // 1, 2, 3
console.log(...ary); // 1 2 3
console.log(1, 2, 3)
// 方法一
let ary1 = [1, 2, 3];
let ary2 = [3, 4, 5];
let ary3 = [...ary1, ...ary2];
// 方法二
ary1.push(...ary2);
let oDivs = document.getElementsByTagName('div');
oDivs = [...oDivs];
let arrayLike = {
'0': 'a',
'1': 'b',
'2': 'c',
length: 3
};
let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']
let arrayLike = {
"0": 1,
"1": 2,
"length": 2 }
let newAry = Array.from(aryLike, item => item *2)
let ary = [{
id: 1,
name: '张三'
}, {
id: 2,
name: '李四'
}];
let target = ary.find((item, index) => item.id == 2);
//找数组里面符合条件的值,当数组中元素id等于2的查找出来,注意,只会匹配第一个
(4)
实例方法:findIndex()
let ary = [1, 5, 10, 15];
let index = ary.findIndex((value, index) => value > 9);
console.log(index); // 2
[1, 2, 3].includes(2) // true
[1, 2, 3].includes(4) // false
let name = `zhangsan`;
let name = '张三';
let sayHello = `hello,my name is ${name}`; // hello, my name is zhangsan
let result = {
name: 'zhangsan',
age: 20,
sex: '男' }
let html = `
${result.name}
${result.age}
${result.sex}
`;
const sayHello = function () {
return '哈哈哈哈 追不到我吧 我就是这么强大';
};
let greet = `${sayHello()} 哈哈哈哈`;
console.log(greet); // 哈哈哈哈 追不到我吧 我就是这么强大 哈哈哈哈
let str = 'Hello world!';
str.startsWith('Hello') // true
str.endsWith('!') // true
'x'.repeat(3) // "xxx"
'hello'.repeat(2) // "hellohello"
const s = new Set();
const set = new Set([1, 2, 3, 4, 4]);
const s = new Set();
s.add(1).add(2).add(3); // 向 set 结构中添加值
s.delete(2) // 删除 set 结构中的2值
s.has(1) // 表示 set 结构中是否有1这个值 返回布尔值
s.clear() // 清除 set 结构中的所有值
注意:删除的是元素的值,不是代表的索引
Set.prototype.keys()
:返回键名的遍历器Set.prototype.values()
:返回键值的遍历器Set.prototype.entries()
:返回键值对的遍历器Set.prototype.forEach()
:使用回调函数遍历每个成员
Set
的遍历顺序就是插入顺序。这个特性有时非常有用,比如使用 Set 保存一个回调函数列表,调用时就能保证按照添加顺序调用。
forEach
方法的参数就是一个处理函数。
s.forEach(value => console.log(value))
1. 去除数组重复成员
// 去除数组的重复成员
[...new Set(array)]
2. 去除字符串里面的重复字符
[...new Set('ababbc')].join('')
// "abc"
3. Array.from
方法可以将 Set 结构转为数组
function dedupe(array) {
return Array.from(new Set(array));
}
dedupe([1, 1, 2, 3]) // [1, 2, 3]
let a = new Set([1, 2, 3]);
let b = new Set([4, 3, 2]);
// 并集
let union = new Set([...a, ...b]);
// Set {1, 2, 3, 4}
// 交集
let intersect = new Set([...a].filter(x => b.has(x)));
// set {2, 3}
// (a 相对于 b 的)差集
let difference = new Set([...a].filter(x => !b.has(x)));
// Set {1}
// 方法一
let set = new Set([1, 2, 3]);
set = new Set([...set].map(val => val * 2));
// set的值是2, 4, 6
// 方法二
let set = new Set([1, 2, 3]);
set = new Set(Array.from(set, val => val * 2));
// set的值是2, 4, 6