【Day1】js高级:涉及let/var/const的异同,解构,rest,拓展运算符的相关知识点


一、前言

最近在复习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 ]

小结:

  1. 当一个数据不会变化的时候,我们就要使用const来声明
  2. 常量的恒定不变,只是相对于值类型来说的,引用类型还是能改变其属性

三、模板字符串

在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);
        //===表示引用地址相同

八、总结

以上就是今天要讲的内容。下一部分会总结一下箭头函数啦。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

TeresaPeng_zju

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值