1、 let和const命令
es5只有全局作用域和函数作用域,没有块级作用域。
下面看一个例子:
for(var i = 0;i < 10;i++){
//...
}
alert(i) //10;
对于有块级作用域的语言来说,for语句初始化变量的表达式所定义的的变量,只会存在于循环的环境中。然而对于JavaScript来说,由for语句创建的变量i,即使在for语句循环结束后,也依旧存在于外部的执行环境中,这就是所说的变量提升。
es6新增了let和const命令,用来声明变量和常量,let命令只在所在的代码块内有效。
例子:
{
let a = 10;
var b = 1;
}
console.log(a) //ReferenceError:a is not defined.
console.log(b) //1
在上面的代码块之中,分别用let和var声明了两个变量。在代码块之外调用这两个变量,
发现let声明的变量错误,因为let的作用域是在它所在的当前代码块,不会被提升到当前函数的最顶部。
常见的面试题:
var a = [];
for (var i = 0;i < 10;i++){
a[i] = function(){
console.log(i);
}
}
a[6](); //10
上面的代码中,变量i是var声明的,在全局范围内都是有效的,所以全局只有一个变量i,每一次循环,变量i的值都会发生变化,被赋给数组a的函数内部的console.log(i),里面的i指向的就是全局的i。也就是说,所有数组a的成员里面的i,指向的都是同一个i,导致运行时的输出的时最后一轮的i的值,也就是10.
如何使a[6]()输出的是6哪?
//es5闭包解决循环绑定问题
var a = [];
for (var i = 0;i < 10;i++){
a[i] = (function(value){
return function(){
console.log(value)
}
})(i)
}
a[6](); //6
//es6
//使用let,声明的变量仅在块级作用域内有效
var a = [];
for (let i = 0;i < 10;i++){
a[i] = function(){
console.log(i)
}
}
a[6](); //6
上面代码中,变量i是let声明的,当前的i只在本轮循环有效,所以每一次循环的i其实都是一个新的变量,所以最后输出的是6。
因为JavaScript引擎内部会记住上一轮循环的值,初始化本轮的变量i时,就在上一轮循环的基础上运算。
另外,for循环的一个特别的地方,就是设置循环变量的部分是一个父作用域,而循环体内部是一个单独的子作用域。
for(let i =0;i < 3;i++){
let i = 'abc';
console.log(i)
}
//abc
//abc
//abc
上面代码输出3次abc。这说明函数内部的变量i与循环变量i不在同一个作用域内,有各自单独的作用域。
const声明一个只读的常量。一旦声明常量的值就不能改变.
const一旦声明变量,就必须立即初始化,不能留到以后赋值。
const PI = 3.1415;
PI; //3.1415
PI = 3; // TypeError: Assignment to constant variable
const foo;//SyntaxError: Missing initializer in const declaration
2、变量的解构赋值
什么是解构?
解构不是构造一个新的对象或者数组,而是逐个拆分现有的对象或数组、字符串,来提取需要的数据。
es6中被解构的数据项位于赋值运算符=的右侧,可以是数组、对象、字符串。
2.1、数组的解构
数组解构:使用一个数组作为一个数据项,根据数组模式(只要等号两边的模式相同,左边的变量就会赋予对应的值)从这个数组里面提取提取一个或者多个变量赋值。
基本用法:
//把数组中所有的数值赋给一个个单独的变量
let [a,b,c] = [1,2,3];
a //1
b //2
c //3
//第一个不提取,其余都提取
let [,b,c] = ['li','yong','good'];
b //'yong'
c //'good'
//中间第二个不提取,其余都提取
let [a,,c] = ['li','yong','good'];
a //'li'
c //'good'
//最后一个不提取,其余都提取(不完全解构)
let [a,b] = ['li','yong','good']
a //'li'
b //'good'
//第一个提取,其余的放到一个数组里,要用到rest操作符
//rest操作符:用来获取多余的参数,将多余的参数放入数组中。
let [head,...tail] = [1,2,3,4];
head //1
tail //[2,3,4]
默认值:
解构赋值允许指定默认值:
let [foo = true] = [];
foo //true
let [x,y = 'yong'] = ['li'];
x //'li'
y //'yong'
let [x,y = 'yong'] = ['li',undefined];
x //'li'
y //'yong'
let [x = 'li'] = [null]
x //null
如果一个数组成员是null,默认值不会生效。
以上代码中,undefined 和 null的区别:
undefined使用默认值;
null是有值,但值是空。
2.2、对象的解构
对象的解构与数组的解构不同,数组的元素是按次序排列的,变量的取值由它的位置决定;对象的属性没有次序,变量必须与属性同名,才能取到正确的值。
基本用法:
//变量名和属性同名
let {a,b} = {a:'li',b:'yong'}
a //'li'
b //'yong'
//变量名与属性名不一致
let {a:xxx} = {a:'li',b:'yong'}
xxx //'li'
对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。
默认值:
let {x,y = 5} = {x:1};
x //1
y //5
let {x = 3} = {x: undefined};
x //3
let {x = 3} = {x:null};
x //null
如果将一个已经声明的变量用于解构赋值,必须小心
//错误的写法
let x;
{x} = {x:1};// SyntaxError: Unexpected token
上面代码的写法会报错,因为JavaScript引擎会将{x}理解成一个代码块,从而发生语法错误。
//正确的写法
let x;
({x} = {x:1});
x //1
2.3 字符串的解构
字符串可以解构赋值。因为字符串被转换成一个类似数组的对象。
const [a,b] = 'yes';
a //'y'
b //'e'
c //'s'
2.4 函数参数的解构
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]
上面代码中,函数move的参数是一个对象,通过对这个对象进行解构,得到变量x 和 y 的值。如果解构失败, x 和 y 等于认值。
注意下面的写法:
function move({x, y} = {x: 0, y: 0}){
return [x, y];
}
move({x: 3,y: 8}) //[3,8]
move({x:3}) //[3, undefined]
move({}) //[undefined,undefined]
move() //[0, 0]
上面的代码是为函数move的参数指定默认值,而不是为变量x 和 y 指定默认值,所以得到与前一种写法不同的结果。
写了那么多,来总结一下在工作中的常用的例子:
- 交换变量的值
let x = 1;
let y = 2;
[x, y] = [y, x];
x //2
y //1
上面的代码交换变量x 和 y 的值。
- 从函数返回多个值
函数返回多个值,只能将它们放在数组或对象里返回。
有了解构赋值,取出这些值就非常方便了。
//返回一个数组
function example(){
return [1,2,3];
}
let [a, b, c] = example();
//返回一个对象
function example(0{
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数据
let jsonData = {
id : 42,
status: 'ok',
data : [77,909]
};
let { id, status, data:number } = jsonData;
id // 42
status // 'ok'
number // [77,909]
- 输入模块的指定方法
加载模块时,指定输入哪些方法。
const { SourceMapConsumer, SourceNode } = require("source-map");
3、模板字符串
es6引入模板字符串(``),进行字符串的拼接,对于字符串中嵌入变量,使用${}。
var str = '这是字符串变量';
$('#body').append(`liyong,${str},good`);
es5字符串查找:
只有indexOf()方法,可以用来确定一个字符串是否包含里一个字符串。返回子字符串的位置(如果没有找到该子字符串,则返回-1)
var d = 'this is a line';
d.indexOf('is') //2
es6提供了一种查找子字符串的方法:includes()
-includes(): 返回布尔值,表示是否找到了参数字符串。
var d = 'this is a line';
d.includes('is') //true
4、数组的知识
4.1数组的扩展运算符
//将一个数组转化为用逗号分割的参数序列
console.log([1,...[2,3,4],5]) //[1,2,3,4,5]
4.2、复制数组
数组是复合的数据类型,直接复制的话,只是复制的指向底层数据结构的指针,而不是克隆了一个新的数组。
es5中数组的复制;
const a1 = [1,2];
const a2 = a1;
a2[0] = 2;
a1; //[2,2]
上面代码,a2不是a1的克隆,而是指向同一个数据的另一个指针。修改a2,会直接导致a1的变化。
另外一种es5的方法:
const a1 = [1,2];
const a2 = a1.concat();
a2[0] = 2;
a1; //[1,2]
上面代码中,a1会返回原数组的克隆,再修改a2就不会对a1产生影响。
es6扩展运算符的复制数组的写法:
const a1 = [1,2];
const a2 = [...a1];
a2[0] = 2;
a1; //[1,2]
4.3、合并数组
数组合并的写法:
//es5
const b1 = [3,4,5]
[1,2].concat(b1) //[1,2,3,4,5]
//es6
[1,2, ...b1] //[1,2,3,4,5]
4.4、Array.from()
将一组类似数组的对象(一组Dom节点)转为真正的数组
let arrayLike = {
'0':'li',
'1':'yong',
length:2
}
//es5的写法
let arr = [].slice.call(arrayLike); //['li','yong']
let arr = Array.prototype.slice.call(arrayLike);//['li','yong']
//es6的写法
let arr = Array.from(arrayLike); //['li','yong']
4.5、Array.of()
用于将一组值,转换为数组。
Array.of(3,11,8) //[3,11,8]
4.6、数组实例的find()和findIndex()
数组实例的find方法,用于找出第一个符合条件的数组成员。它的参数是一个回调函数,所有的数组成员依次执行该回调函数,直到找出第一个返回值为true的成员,然后返回该成员。如果没有符合条件的成员,则返回undefined.
[1,4,-5,10].find((n) => n < 0) //-5
以上代码找出数组中第一个小于0的成员
4.7、数组实例的includes()
与字符串的includes方法类似,Array.prototype.includes方法返回一个布尔值。
[1,2,3].includes(2); //true
[1,2,3].includes(4); //false
4.8、数组实例的entries(),keys(),values()
keys()是对键名的遍历、values()是对键值的遍历、entries()是对键值对的遍历
let arr = ['li','yong'];
for(let [index,value] of arr.entries()){
console.log(`索引 ${index} : 键值 ${value}`)
}
//索引 0 : 键值 li
//索引 1 : 键值 yong
4.9、数组的遍历方法
- forEach:会自动省略为空的数组元素
let arr = ['li','yong','good',''];
arr.forEach((value,index)=>console.log(`索引${index} : 键值${value}`))
//索引0 : 键值li
// 索引1 : 键值yong
// 索引2 : 键值good
- filter:创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有的元素。
filter()不会对空数组进行检测
filter()不会改变原始数组
let arr = ['li','yong','good'];
arr.filter(x=>{if(x == 'li'){return console.log(`筛选的是:${x}`)}})
4.10、数组转换字符串
- join()方法
let arr = ['li','yong','good'];
console.log(arr.join('|'))
//li|yong|good
- toString()方法
let arr = ['li','yong','good'];
console.log(arr.toString())
//li,yong,good
5、对象的扩展
5.1、对象的属性和方法的简写
const name = 'li';
const age = 00;
const o = {name, age,method(){
console.log(this.name)
}}
o.method() //'li'
5.2、Object.is()
es5 比较两个值是否相等,相等运算符(==)和严格相等运算符(===)。前者的不足是,会自动转换数据类型,后者的NaN不等于自身,以及+0等于-0。
es6 提出"Same-value-equality"(同值相等)。Object.is就是比较两个值是否严格相等。
Object.is(true,1) //false
Object.is(true,true) //true
Object.is(+0,-0) //false
Object.is(NaN,NaN) //true
5.3、Object.assign()
Object.assign方法用于对象的 合并,将源对象(source)的所有的可枚举属性,复制到目标对象(target)。
const target = {a: 1};
const source1 = {b: 2};
const source2 = {c: 3};
Object.assign(target, source1, source2)
target //{a:1, b:2, c:3}
5.4、Object.assign()
- 为对象添加属性
const bbbb = {name:'li'};
Object.assign(bbbb,{age:18,height:00})
bbbb;
//{
// age:18
// height:0
// name:"li"
// } -
为对象添加方法
直接将两个函数放在大括号中,再使用assign方法添加到目标对象中。
const bbbb = {name:'li'};
Object.assign(bbbb,{getName(){ return this.name; }, getAge(){ return this.age; }
})
5.5、Object.keys(),Object.values(),Object.entries(),
返回一个数组
Object.entries的用途是遍历对象的属性。
const obj = {name:'li',age:00 };
for( let [key,value] of Object.entries(obj) ){
console.log(`属性${JSON.stringify(key)}:属性值${value}`)
}
//属性"name":属性值li
//属性"age":属性值00