浑浑噩噩了五年,结了婚生了孩子,时间不够了才知道好好学习~~~~~~
let和const
-
在ES6之前只存在全局作用域和函数作用域
- 导致变量提升
- 在代码块外仍可以访问
-
ES新增了块级作用域(变量可执行上下文为一个代码块中即一对花括号中)
- 不存在变量提升
- 存在暂时性死区
- 定义变量之前,该变量不能访问
- 所以typeOf也不是绝对安全的(使用var定义使用typeOf绝对安全,可返回undefined)
- 不能重复声明
function foo(){ let flag = true; if(true){ let flag = false; // 这里可以因为不在同一作用域块 } var flag = true; // Identifier 'flag' has already been declared } 复制代码
- 在全局作用域下使用var定义的变量是全局对象(window)的属性,但是使用let定义的变量不再是全局对象的属性
-
let的特点:
a. 不会导致for循环索引值泄漏
var arr = []; for(var i=0; i<10; i++){ arr[i] = function(){ console.log(i) } } arr[1]() // 10 // 通过var声明的索引i是一个全局变量,每一次循环,全局变量i都会发生改变。而数组arr所有成员里面的i都指向同一个i,当循环结束后,全局变量i的值已经变为10 var arr = []; for(let i=0; i<10; i++){ arr[i] = function(){ console.log(i) } } arr[1]() // 1 // 通过let定义的索引值i,只在当前循环内有效,实际上每一轮循环中的i都是一个新的变量,而且最关键的是JavaScript引擎能够记住上一轮循环的值,所以在本轮循环开始时,会基于上一轮循环计算,从而索引i的值会递增。 复制代码
- 为什么通过let声明的变量i在循环体外,仍然可以访问呢?这是因为arr数组的每个成员都是一个函数,对变量i的引用构成了一个闭包,所以在循环体外调用函数时仍然可以访问到i
- for循环还有一个特别之处,声明变量的那部分(小括号内部)是一个父作用域,循环体内部是一个单独的子作用域
b. 避免变量提升导致的变量被覆盖问题
var name = 'yangf'; function foo(){ console.log(name); // undefind if(false){ var name = 'yangf'; } } foo(); 复制代码
c. 代替立即执行函数IIFE
- 使用IIFE的目的主要是避免污染当前作用域内的变量,而使用块级作用域则可以直接避免这个问题
(function(){ var name = ... }()) // 块级作用域 { let name = ... } 复制代码
d.const基本和let的特点一致,还有个特别之处是,因为const定义的是常量,所以定义了不能改变
- 对于基本类型的变量来说,变量就保存着内存地址的值,因此不能直接修改;而对于引用类型的变量来说,变量保存的是一个指向数据内存地址的指针,只要该指针固定不变,我们就可以改变数据本身的值
const person = {
name: 'yangf'
}
person.age = '18';
console.log(person) // {name: "yangf", age: "18"}
person = {
name: 'xiaoliu'
}; // Assignment to constant variable
复制代码
解构赋值
将值从数组或者对象中提取出来,并赋值到不同的变量中。
-
数组的解构赋值
数组的解构赋值是基于数组元素的索引
a. 使用的是模式匹配,只要等号两边数组的模式相同,右边数组的值就会相应赋给左边数组的变量
let [num1,num2] = [10,11];
console.log(num1);// 10
console.log(num2);// 11
复制代码
b. 可以只解构出感兴趣的值,对于不感兴趣的值使用逗号作为占位符,而不指定变量名
let [,num2] = [10,11];
console.log(num2);// 11
复制代码
c. 当右边的数组的值不足以将左边数组的值全部赋值时,会解构失败,对应的值就等于“undefined”
let [num1,num2,num3] = [10,11];
console.log(num1);// 10
console.log(num2);// 11
console.log(num3);// undefind
// 为防止值为undefined,可以在左侧的数组添加默认值
let [num1,num2 = 8,num3 = 12] = [10,11];
console.log(num1);// 10
console.log(num2);// 11
console.log(num3);// 12
// 只有在解构出现undefind的时候默认值才生效,所以这里num2还是为11
复制代码
-
项目中使用
- 交换变量
var a = 1; var b = 2; // es6之前想要交换变量,需要定义一个临时变量 var tmp = a; a = b; b = tmp; // es6之后 [a, b] = [b, a] 复制代码
- 解析函数返回的数组
- 函数返回数组是一个很常见的场景,在获取数组后,我们经常会提取数组中的元素进行后续处理
function fun(){ return [10, 11] } let [num1, num2] = fun(); console.log(num1); // 10 console.log(num2); // 11 复制代码
- 嵌套数组的解构
let [num1, num2, [num3]] = [10, [11, 12], [13]]; console.log(num1); //10 console.log(num2); //[11, 12] console.log(num3); //13 复制代码
- 函数参数解构
- 当函数的参数为数组类型时,可以将实参和形参进行解构
function fun([arg1, arg2]){ console.log(arg1); //10 console.log(arg2); //11 } fun([10, 11]); 复制代码
-
对象的解构赋值
右侧解构对象的属性名和左侧定义对象的变量名必须相同
-
对象解构赋值原理和使用
- 没有匹配到的变量名在解构时赋值为’undefined’
- key和value名称相同时可以简写,不同时左侧必须严格按照key:value格式书写
- 原理:找到左右两侧相同的属性名(key),然后再赋给对应的变量(value),真正被赋值的是value部分,并不是key的部分
- 解构赋值的两个对象的属性不必是一一对应的,只要key部分名字相同就能解构成功
let {m, n, o} = {m: 'yangf', n: '18'};
console.log(m); // 'yangf'
console.log(n); // '18'
console.log(o); // undefined
let {m:name,n:age} = {m:'yangf',n:18}
console.log(name); // 'yangf'
console.log(age); // '18'
console.log(m); // ReferenceError: m is not defined
let {n:age,m:name} = {m:'yangf',n:18}
console.log(name); // 'yangf'
console.log(age); // '1
复制代码
-
对象解构的默认值
- 解构的时候可以设置默认值,默认值生效的条件是对应的属性值严格等于undefined
- 属性名和变量名不同时,默认值是给变量的
let {m, n=18, o:j=1} = {m: 'yangf', n: null};
console.log(m); // 'yangf'
console.log(n); // null (null和undefined不是严格相等)
console.log(j); // 1
console.log(o); // ReferenceError: m is not defined
复制代码
-
嵌套对象的解构
- 解构时从外层向内层逐步进行
- 每一层都遵循相同的解构规则
- 当父层对象对应的属性不存在,而解构子层对象时,会出错并抛出异常
let {x:[y,{z:name}]} = {x:['hello',{z:'world'}]};
console.log(y); // 'hello'
console.log(name); // 'world'
console.log(z); // ReferenceError: z is not defined
let {o:[y,{z:name}]} = {x:['hello',{z:'world'}]};
console.log(y); // undefined is not iterable
复制代码
右侧外层的属性名是x,左侧外层属性名是o,两者并不匹配,所以o会解构得到“undefined”。而对undefined再次解构想要获取y属性时,相当于调用undefined.y,会抛出异常
-
选择性解构对象的属性
- 假如一个对象有很多通用的函数,如果只想使用其中的几个函数,可以使用解构赋值
let {min,max} = Math;
console.log(min(1,3)); //1
console.log(max(1,3)); // 3
// 如果只想使用Math对象的min()函数和max()函数,min变量和max变量解构后的值就是Math.min()函数和Math.max()函数,在后面的代码中可以直接使用
复制代码
-
函数参数解构
- 当函数的参数是一个复杂的对象类型时,可以通过解构去获得想要获取的值
- 有时候后台返回一些无用的参数,可以使用解构赋值得到我们想要的某一些参数
function fun({oldName,{newName:{firstName:name}}}) {
console.log(oldName + 'is' + name);
}
let people = {
age:18,
oldName:'yangf',
newName:{
firstName:'yang',
lastName:'feng'
}
};
fun(people);// yangf is yang
复制代码
扩展运算符
用3个点表示(…),用于将一个数组或类数组对象转换为用逗号分隔的值序列
const array = [1,2,3,4];
const str = 'string';
console.log(...array); // 1 2 3 4
console.log(...str);// s t r i n g
复制代码
-
扩展运算符替代apply函数
a. 将数组转化为函数参数
console.log(Math.min(...[1,2,3])); // 1
function add(num1,num2){
return num1 + num2;
}
const arr = [1, 2];
add(...arr); // 3
复制代码
-
扩展运算符代替concat函数合并数组
const arr1 = [1, 2];
const arr2 = [3, 4];
console.log([...arr1, ...arr2]); // [1, 2, 3, 4]
复制代码
-
扩展运算符转换Set得到去重的数组
const arr = [1, 2, 3, 4, 4, 4];
console.log([...new Set(arr)]); // [1, 2, 3, 4]
复制代码
-
扩展运算符用于对象和数组的克隆
// 例1 let obj = {name: 'yangf'}; let copyObj = {...obj}; copyObj.name = 'yfeng'; console.log(obj.name); // yangf // 例2 let obj = {people:{name: 'yangf'}}; let copyObj = {...obj}; copyObj.people.name = 'yfeng'; console.log(obj.people.name); // yfeng 复制代码
a. 例1对copyObj进行改变,不会影响obj
b. 例2对copyObj进行改变,影响了obj
c. 说明扩展运算符不是严格意义上的深拷贝
d. 如果数组的元素或者对象的属性是基本数据类型,则支持深拷贝;如果是引用数据类型,则不支持深拷贝
e. 扩展运算符可以代替concat函数,所以concat函数也具备以上特性
rest运算符
同样使用3个点表示(…),作用与扩展运算符相反,用于将以逗号分隔的值序列转换成数组
-
rest运算符与解构组合使用
-
解构会将相同数据结构对应的值赋给对应的变量,当我们想将其中的一部分值统一赋给一个变量时,可以使用rest运算符
a. 想要使用rest运算符进行解构,则rest运算符对应的变量应该放在最后一位,否则就会抛出异常
b. 对象和数组使用方法相同
let arr = [1, 2, 3, 4]; let [arg1,...arg2] = arr; console.log(arg1); // 1 console.log(arg2); // [2, 3, 4] let arr = [1, 2, 3, 4]; let [...arg1,arg2] = arr; // SyntaxError: Rest element must be last element 复制代码
-
rest运算符代替arguments处理函数参数
a. 当我们不确定函数传人参数的个数时使用
-
function fun() {
for(let arg of arguments) {
console.log(arg)
}
};
fun(1,2,3,4); // 1 2 3 4
function fun(...args) {
for(let arg of args) {
console.log(arg)
}
};
fun(1,2,3,4); // 1 2 3 4
复制代码
扩展运算符和rest运算符两者是互为逆运算的,扩展运算符是将数组分割成单独的序列,而rest运算符是将单独的序列合并成一个数组
- 当3个点(…)出现在函数的形参上或者出现在赋值等号的左侧,则表示它为rest运算符。
- 当3个点(…)出现在函数的实参上或者出现在赋值等号的右侧,则表示它为扩展运算符。
模板字符串
-
字符串输出
- 使用加号拼接字符串会丢失缩紧和换行符
- 使用模板字符串则不会
const str = 'my name is yangf,'+
'i am a girl';
console.log(str); // my name is yangf,i am a girl
const str = `my name is yangf,
i am a girl`;
console.log(str);
/*
my name is yangf,
i am a girl
*/
复制代码
-
字符串变量值传递
-
都是使用${}包裹变量
a. 模板字符串中传递基本数据类型的变量
b. 模板字符串中传递表达式
c. 模板字符串中传递复杂引用数据类型的变量
-
const name = 'yangf';
console.log(`my name is ${name}`); // my name is yangf
// 表达式调用
const x = 1, y = 2;
console.log(`${x} + ${y * 2} = ${x + y *2}`); // 1 + 4 = 5
// 函数调用
function fun() {
return 'name is';
};
console.log(`my ${fun()} yangf`); // my name is yangf
// 嵌套使用
const people = [{
name:'xiaom',
age:'18'
}, {
name:'xiaoh',
age:'20'
}];
const peopleTable = function(people){
return `<table>${people.map(item => `
<tr><td>${item.name}<td></tr>
<tr><td>${item.age}<td></tr>
`).join('')}</table>`
};
console.log(peopleTable(people));
/*
<table>
<tr><td>xiaom<td></tr>
<tr><td>18<td></tr>
<tr><td>xiaoh<td></tr>
<tr><td>20<td></tr>
</table>
复制代码
箭头函数
省去了function关键字,函数体会被一个大括号括起来
如果函数的参数只有一个,则可以省略小括号
如果函数体只有一行,则可以省略大括号和return关键字
-
特点
- 语法简洁
- 不绑定this
- this永远指向函数的调用者
- 在箭头函数中,this指向的是定义时所在的对象,而不是使用时所在的对象
- 不支持call和apply
- 箭头函数并没有自己的this,而是继承父作用域中的this
- 不绑定arguments
- 在箭头函数中使用arguments,同样也就无法使用caller和callee属性
- arguments中有一个callee的属性,指向函数本身
- caller属性中保存着调用当前函数的函数的引用
- 可以使用rest运算符来获取参数
- 支持嵌套
- 一个参数会以管道的形式经过两个函数处理
- 第一个函数处理完的输出将作为第二个函数的输入
- 两个函数运算完后输出最后的结果
[1,3,2,4].sort((x,y)=>x-y);//[1,2,3,4]
const person = {
name:'yangf',
age:18,
say:function(){
console.log(`my name is ${this.name}`)
},
say1:()=>{
console.log(`my age is ${this.age}`)
}
};
// this指向函数的调用体,即person本
console.log(person.say());// my name is yangf
// 在执行person.say1()函数时,say1()函数中的this会指向外层作用域,而person的父作用域就是window
console.log(person.say1());// my age is undefined
const fun = ()=>{
console.log(arguments);
};
fun(1,2);// ReferenceError: arguments is not defined
const fun = (...args)=>{
console.log(args);
};
fun(1,2);// [1,2]
// 嵌套
const fun = (...args) => val => args.reduce((a,b) => b(a),val);
const num1 = a => a + 1; //6
const num2 = a => a * 2;
const add = fun(num1,num2); // 调用了fun()函数,并传入num1和num2两个参数,返回的是一个函数,在函数中使用reduce()函数先后调用传入的两个处理函数。
console.log(add(5)); //fun()函数中的val为5,在第一次执行reduce()函数时,a为5,b为num1()函数,实际相当于执行5 + 1 = 6,并返回了计算结果
// 在第二次执行reduce()函数时,a为上一次返回的结果6,b为num2()函数,实际相当于执行6×2 =12,因此最后输出
复制代码
-
箭头函数不适用的场景
-
不适合作为对象的函数
a. 使用箭头函数定义对象字面量的函数,其中的this将会指向外层作用域,并不会指向对象本身
-
不能作为构造函数,不能使用new操作符
a. 构造函数是通过new操作符生成对象实例的,生成实例的过程也是通过构造函数给实例绑定this的过程,而箭头函数没有自己的this
-
没有prototype属性
a. 因为在箭头函数中是没有this的,也就不存在自己的作用域
-
不适合将原型函数定义成箭头函数
a. 在给构造函数添加原型函数时,如果使用箭头函数,其中的this会指向全局作用域window,而并不会指向构造函数
-