文章目录
一、前言
最近在复习js高级,会做一些知识点的总结。本篇文章会谈到let/var/const的异同,各种不同类型的解构,rest,拓展运算符的相关知识点。那么就开始吧~
首先,在讲解js高级前,我们不得不谈到ES6。Javscript分为三大部分:ECMAScript + DOM + BOM,而我们说的ES就是指 ECMAScript。
ECMAScript就是一种语法标准,规定了这个语言的语法要如何书写,何种语法有何种作用。它既是一个历史名词,也是一个泛指,含义是 5.1 版以后的 JavaScript 的下一代标准,涵盖了 ES2015、ES2016、ES2017 等等,而 ES2015 则是正式名称,特指该年发布的正式版本的语言标准。
也就是说从2015年以后推出的新标准,现在统称为ES6。
二、ES6变量声明
2.1、var的弊端
使用var关键字声明变量的弊端:
1、var声明的变量有预解析,造成逻辑混乱,可以先使用,后声明
2、var可以重复声明,不利于多人协作开发
3、var用在for循环条件中,造成全局变量i污染的问题
4、var 声明的变量没有块级作用域(ES5中的作用域:全局和局部)
// 1. 可以先使用再声明
console.log(a);
var a = 10;
// 2. 可以重复声明
var a = 10;
console.log(a);
//3. var 有预解析
//4.在for循环中使用,会造成全局变量i的污染
var i;
for (i = 0; i < 5; i++) {// i是全局变量 没有函数,没作用域
console.log(i);
}
console.log(i);
2.2、let关键字
ES6推出了一个新的声明变量的关键字,它的基本使用如下:
let 变量 = 值;
例如:
let age = 15;
let关键字的必要性:
var关键字其实是有不少的问题的,比如var的变量会提升,提升后会造成一定的混乱,你可以在变量声明之前使用这个变量,而此时你对于这个变量是否有值是不确定的,所以var的变量提升后其实是有一定的问题的。
因此ES6中为了统一并提高代码的安全性,引入了let关键字来代替var声明变量
- let声明的变量不会提升,必须要在声明后才能使用
console.log(data) // Uncaught ReferenceError: Cannot access 'data' before initialization
let data = 10;
- let声明的变量不能重复声明
let data = 10;
let data = 20; // Uncaught SyntaxError: Identifier 'data' has already been declared
- let声明的变量存在块级作用域
if(false){
let data = 10;
}
console.log(data) // Uncaught ReferenceError: data is not defined
块级作用域:
块指的是代码块,一对 {}
之间就是一个代码块。变量的有效范围只在这对大括号之间,就是块级作用域
。
这里以一个点击事件为例,来说明块级作用域非常有必要:
// 需求: 点击按钮,显示对应的索引
var btns = document.getElementsByTagName("button");
console.log(btns);
for (var i = 0; i < btns.length; i++) {
// for循环同步
btns[i].onclick = function () {
// 点击事件异步
// 点击的时候,for循环已经执行完毕, 全局变量i永远变成5
console.log(i);
}
}
此时我们点击按钮,输出的i不会是每个按钮对应的按钮,而是按钮的个数(即5)。就是因为for里面的变量是var声明的,没有形成块级作用域,每次访问i都是访问的全局的i。如果我要在事件里面得到按钮的索引,就需要别的方法来实现。
如果用此前的ES5去解决,我们可以使用setAttribute()
和getAttribute()
将变量存起来,也可以直接在对象身上添加一个myIndex
属性。
var btns = document.getElementsByTagName("button");
console.log(btns);
for (var i = 0; i < btns.length; i++) {
// for循环同步
//btns[i].setAttribute("myIndex", i);// 标签属性
btns[i].myIndex = i;// 对象属性
btns[i].onclick = function () {
// 点击事件异步
// 点击的时候,for循环已经执行完毕, 全局变量i永远变成5
// console.log(i);
// console.log(this.getAttribute("myIndex"));
console.log(this.myIndex);
}
}
如果我们换成ES6中的let:
for (let i = 0; i < btns.length; i++) {
/*
每一次遍历,都会创建一个块级作用域 i=0的时候的i 和 i=1时候这个i 不是一个变量
{let i=0; btns[0]=fn 打印0}
{let i=1; btns[1]=fn 打印1}
{let i=2; btns[2]=fn 打印2}
{let i=3; btns[3]=fn 打印3}
{let i=4; btns[4]=fn 打印4}
*/
btns[i].onclick = function () {
console.log(i);
}
}
此时我们点击按钮,就能输出按钮对应的索引了。这是因为我们使用了let关键字,let会在{}之间形成一个块级作用域,for循环执行多少次,我们就会形成多少个块级作用域,每个i对应1个,当我们在事件里面获取i的时候,i会到事件函数的上级作用域找,刚好就是每个i对应的块级作用域。我们就简单地得到了每个按钮对应的索引。
所以,let的特点:
1、let声明的变量没有预解析,不会有变量提升
2、同一作用域let不可以重复定义同一个变量
3、let用在for循环条件中,不会造成for 循环的污染的问题
4、let声明的变量有块级作用域(ES6中的作用域:全局和局部还有块级作用域)
2.3、const关键字
ES6中,为了让程序可以执行起来更加高效,推出了const关键字来声明一个只读的常量。
常量具备以下特点:
1、一旦声明就必须赋值
2、一旦赋值就不能修改
3、常量的作用域和let声明的变量作用域一样 块级作用域
4、没有预解析
5、引用类型的值可以修改
那么我们该怎么使用const关键字呢,其固定语法如下:
const 常量名 = 值;
例如:
const data = 100;
如果我们声明了常量不赋值:
const data; // Uncaught SyntaxError: Missing initializer in const declaration
如果我们想要修改常量:
const data = 100;
data = 200; // Uncaught TypeError: Assignment to constant variable
所以当我们在一段代码中有一个数据在执行过程中不会改变,就可以使用常量来标识。
值得注意的是,const只能保证值类型的数据不能被修改,如果是引用类型的值则不行:
const Arr = [10, 0, 30];
//const Arr = [10,0,30]赋值的是引用地址()。
Arr[1] = 20;
console.log(Arr); //[ 10, 20, 30 ]
小结:
- 当一个数据不会变化的时候,我们就要使用const来声明
- 常量的恒定不变,只是相对于值类型来说的,引用类型还是能改变其属性
三、模板字符串
在ES5中,当我们需要把多个字符串和多个变量拼接到一起的时候,写法还是比较复杂的
let name = '狗蛋',age = 12,gender = '男'
let str = "大家好,我叫" + name + ",今年" + age + "岁了,我是一个" + gender + "孩子"
所以在ES6中为了解决这个问题,提供了一种模板字符串
固定用法:
`固定字符${变量或者表达式}`
- 在模板字符串中,可以解析
${}
之间的变量或者表达式 - 在整个字符串中允许换行
以下是在ES6中拼接字符串的例子:
let name = '狗蛋',age = 12,gender = '男'
let str = `大家好,我叫${name},今年${age}岁了,我是一个${gender}孩子`
四、解构语法
解构: 将数组或对象中的值提取出来,赋值给变量。
目的: 简化代码。
4.1、对象解构
let obj = {
name: "luowowo",
age:11,
email:"luowowo@163.com"
};
// 取出所有属性并赋值:
// let name = obj.name;
// let age = obj.age;
// let email = obj.email;
// 现在只需要(等效于上面的写法):
// 等号左边的变量要和对象里面的属性同名,否则没法解构出来
// let {name, email, age} = obj; //{ }中的变量名和obj的属性名一致 完全解构
// 部分解构
// let {name} = obj; // 部分解构
//解构之后重命名
let {name:itsName} = obj; 解构之后重命名为itsName
//将现有对象的方法,赋值到某个变量
let {random}=Math;
console.log(random)//[Function: random]
4.2、数组解构
之前我们是通过索引赋值,现在通过ES6的解构赋值:
let arr1 = [10, 20, 30];
let [a, b, c , d] = arr1;
// 完全解构
console.log(a); //10
console.log(b); //20
console.log(c); //30
// 若解构不成功,变量的值就等于 undefined
console.log(d); //undefined
// 部分解构
let [e] = arr1;
console.log(e); //10
let [ , ,f] = arr1;
console.log(f); //30
//部分解构:使用字符占位
//但是不建议这么写,因为如果有100个数字,写占位符也很麻烦。ES6能帮你简写时才用它!
// 复合解构
let arr2 = [1, 2, [10, 20, 30]];
let [ j, k, [x, y, z]] = arr2;
console.log(j); //1
console.log(k); //2
console.log(x); //10
console.log(y); //20
console.log(z); //30
4.3、字符串解构
let string1 = "xyz";
let [a,b,c] = string1;
console.log(a); //x
console.log(b); //y
console.log(c); //z
string1[1] = "Y";
console.log(string1); // xyz 无法修改
console.log(string1[1]); // y
4.4、应用场景
交换两个变量的值:
// 4.案例: 交换两个变量的值
let a = 10, b = 20;
let c = b;
b = a;
a = c;
console.log(a, b);
// [变量]=数组
[a, b] = [b, a];
console.log(a, b);
4.5、伪数组解构
伪数组本质是一个 Object,而真实的数组是一个 Array。
譬如,str有长度,能进行索引…(有数组的特征),但不能使用forEach()方法,就能证明它是一个伪数组。
// 伪数组: 元素的集合 jQuery对象 字符串
var str = "欧阳三是一个靓仔";
// console.log(str.length);
// console.log(str[2]);
/* str.forEach(function (item, index) {
// str.forEach is not a function 这个报错肯定不是数组
// 不能使用数组方法 又具有数组的特征(长度和索引) 所有叫伪数组-----暂时的理解----面向对象再详细讲
}) */
//伪数组 字符串 的解构赋值
let [a, b, c] = str;
console.log(a, b, c);
let [dom] = document.getElementsByTagName("h3");
console.log(dom);
五、对象的简化写法
当我们在以前定义对象字面量的时候,有如下代码:
let name = '狗蛋',age = 12,gender = '男'
let obj = {
name : name,
age : age,
gender : gender
}
此时我们发现obj的属性名和变量是同样的,可以在ES6中简化为:
let obj = {name,age,gender}
也就是说,如果一个对象的属性名和外面的一个变量名同名,可以直接将变量名作为属性名,并会自动地把变量的值作为属性的值
方法上面也可以进行简化:
let name = 'andy';
let age = 12;
function fn() {
console.log(`my name is ${this.name}`);
}
// es6 写法
let userInfo = {
name,
age,
fn
};
console.log(userInfo);
userInfo.fn();
补充:函数中也可以只写函数名,不写value值。
六、函数参数默认值和参数解构
6.1、函数形参的默认值
ES5里面如果我们想要实现参数可以省略,我们有一个办法是这样做:
function add(a,b,c,d){
a = a || 0;
b = b || 0;
c = c || 0;
d = d || 0;
return a + b + c + d;
}
如果不给函数设置默认值,只传一个值,就会出现如下的结果:
add(2)
//返回2+undefined=NaN
//Not a Number,非数。表示未定义或不可表示的值
此时我们可以把给了默认值的参数省略:
add(10,20) // 30
add(10,20,30) // 60
但是这样的做法比较麻烦,那如果参数很多,那么需要写的代码也很多。对此,ES6中提供了一种更加方便的方式,专门实现参数默认值。参数有了默认值之后就可以在调用的时候省略。
funciton 函数名(参数=默认值){ // 注意当 参数 为 undefined 时 参数 赋值为 默认值
}
上面的例子就可以必成:
function add(a=0,b=0,c=0,d=0){
return a + b + c + d;
}
6.2、函数参数的解构赋值
函数参数解构:
// 参数是一组有次序的值
function f([x, y, z]) {
console.log(x, y, z);
}
f([1, 2, 3]);
// 参数是一组无次序的值
function fn({x, y, z}) { // {x, y, z} = obj 解构
console.log(x, y, z);
}
fn({z: 4, x: 5, y: 6});
6.3、解构赋值指定参数的默认值
function func2({name, age} = {}){ //防止不传实参时候的报错
console.log(name, age);
}
func2(); //undefined undefined
// func2(); //相当于传了一个null {name, age}=null 就会报错
// func2({}); //不会报错,输出:undefined undefined
function func2({name="luowowo", age=11} = {}){ //指定默认值
console.log(name, age);
}
func2(); //luowowo 11
6.4、解析ajax方法
// 解析 $.ajax 方法
/*
$.ajax({
url:"",
type:"GET",
data:{},
success(res){
}
})
*/
/*
示例 :
function add(a) {
// 相当于 var a = 1
console.log(a);
}
add(1); */
function ajaxFn({ url = "", type = "GET", data = {}, success = function () { } } = {}) {
//1.**** let {url, type, data, success} = obj
// 2.注意点1 : 解决报错问题:设置函数参数默认值 let { url, type, data, success } = {}
// 3.注意点2 : 解决undefined问题: 每个参数都设置默认值
console.log(url, type, data, success);
}
/*
//1.**** 函数传一个实参 是 对象 在 函数中可以进行解构赋值
ajaxFn({
url: "xxx.xxxx.com",
type: "GET",
data: { username: "123", password: "123" },
success: function () {
console.log(111);
}
}) */
ajaxFn();
// 2.注意点1 : 如果函数调用不传参 相当于 let { url, type, data, success } = undefined
// 解决报错问题: let { url, type, data, success } = {}
// 3.注意点2 : 获取参数 是 undefined ???
// 解决undefined问题: 每个参数都设置默认值
七、rest 参数和拓展运算符
7.1、rest 参数/剩余参数
arguments 对象:
function fn(){
console.log(arguments);// 伪数组
}
fn(10, 20, 30, 50, 60);
函数形参和实参不对等,使用arguments解决。函数传进来的所有参数可以用arguments取代。但我们发现,它不能使用forEach()
。也就是说,其有数组特征,但是不能使用数组方法,所以是伪数组。
ES6提供了新的方法:使用rest参数搭配的变量是一个数组,该变量将多余的参数放入数组中。(rest是真数组)
注意,rest参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。
// 函数的形参和实参不对等??? arguments 解决
/* function fn() {
console.log(arguments[0]);// 参数集合
//arguments.forEach is not a function 有数组特征不能使用数组方法----伪数组
// arguments.forEach();
}
fn(1, 2, 3, 4); */
// ES6解决
// 1. 基本语法
/*
1. rest参数是一个真数组
2. 重点是... 不是rest rest只是一个名称 不是 语法------(...形参)
3. 可以获取剩余参数 (a,b,...c) ------- 先 将实参和 a ,b 进行匹配, 剩余的参数全部用c接收
4. Rest参数必须是最后一个形参,否则会报错
*/
// function fn(...a) {
// // console.log(rest);
// /* rest.forEach(function (item) {
// console.log(item);
// }) */
// console.log(a);
// }
// fn(1, 2, 3, 4);
// 2. 剩余参数的获取------(a,b,...c)
/* function fn(a, b, ...c) {
console.log(a, b, c);
} */
// 3. Rest参数必须是最后一个形参,否则会报错
/* function fn(a, b, ...c, d) {
console.log(a, b, c);
}
fn(1, 2, 3, 4, 5) */
function add(...rest) {
var sum = 0;
rest.forEach(function (item) {
sum += item
})
return sum;
}
console.log(add(1, 2, 3, 4, 5, 6));
说明:重点是...
而不是rest,rest只是一个形参。
7.2、拓展运算符
ES6中的扩展运算符是一个非常有用的运算符号,可以用来快速展开数组,对象…
它的作用就是可以将数组或者对象展开,拆开成为一个一个单独的数据。
拓展运算符和rest有点像(rest在函数中使用),但不是一回事,二者应用场景不同。
// 快速将一个数组拆开成一个一个的元素
let arr = [1, 2, 3, 4]
console.log(...arr)
// 快速将一个对象里面的数据复制一份到一个新的对象里面
let obj = { name: '狗蛋', age: 12, gender: '男' }
console.log({id:1,birthday:'2020-02-02', ...obj})
// 将一个字符串拆开成为多个单独的字符
let str = 'abc'
console.log(...str)
应用场景:
// 拓展运算符: 快速展开数组或对象-----将数组或者对象中的值 一个一个的拿出来单独使用
// 注意点: 拓展运算符 可能和rest参数有点像, 但是 *不是一回事*
// 1. 拓展运算符展开数组...arr
let arr = [10, 20, 30];
// console.log(...arr);
// 相当于 打印了(arr[0],arr[1],arr[2])
// 1.1 数组中的每个值可以作为方法的参数使用
// console.log(Math.max(1, 2, 5, 4, 8, 3));
// console.log(Math.max(...arr));
// 1.2 数组合并 --- 并且不改变原数组,因为[]就代表一个新的内存空间
/* let arr1 = [40, 50, 60];
let newArr = [...arr, ...arr1];
newArr[0] = 100;
console.log(newArr); */
// 2. 拓展运算符展开对象
let obj1 = {
name: "欧阳三",
age: 20
},
obj2 = {
name: "南宫四",
gender: "男",
email: "ouyangsan132154@163.com"
};
/*
...obj1 相当于其他打印 name: "欧阳三",age: 20
*/
// console.log(...obj1, ...obj2);
// console.log(name: "欧阳三", age: 20);
// 2.1 对象合并
// 注意点: 对象的key不能重复 后面的会覆盖前面的
let newObj = { ...obj2, ...obj1 };
console.log(newObj);
拓展-合并数组的第二个方法Object.assign()
:
// 拓展运算符 : 可以合并对象 数组
// 在解构赋值中使用,拓展运算符可以获取 数组/字符串 剩余的值
// let [a, b, c, ...d] = [10, 20, 30, 40, 50, 60, 70, 80];
// console.log(a, b, c, d);
// let str = "vue开始不会写了";
// let [a, b, ...c] = str;
// console.log(a, b, c);
let obj = {
name: "欧阳三",
age: 80,
gender: "男"
}
let obj1 = {
name: "南宫四",
age: 25,
email: "xxx1545@163.com"
}
/* let newObj = { ...obj, ...obj1 };
console.log(newObj); */
// ES6 的 对象方法 Object.assign 可以做对象合并, 将第二个开始的对象参数 合并到第一个对象参数中
// 注意点: 会改变第一个对象参数
let newObj = Object.assign({}, obj, obj1);//因此,这里设定第一个参数为空对象,就不会改变源对象
// newObj.gender = "女";
console.log(newObj === obj);
//===表示引用地址相同
八、总结
以上就是今天要讲的内容。下一部分会总结一下箭头函数啦。