ES6(一):let和const、模板字符串、函数默认值、剩余参数、扩展运算符、箭头函数

ES6学习:哔站小马哥ES6从入门到精通系列(全23讲),开发必备,推荐必看_哔哩哔哩_bilibili+阮一峰文档ES6 入门教程

一、ES6简介

ECMAScript 和 JavaScript 的关系是,前者是后者的规格,后者是前者的一种实现(另外的 ECMAScript 方言还有 JScript 和 ActionScript)。日常场合,这两个词是可以互换的。ES6 既是一个历史名词,也是一个泛指,含义是 5.1 版以后的 JavaScript 的下一代标准,涵盖了 ES2015、ES2016、ES2017 等等。

二、let和const

1、let不存在变量提升

var命令会发生“变量提升”现象,即变量可以在声明之前使用,值为undefined。但是let命令改变了语法行为,它所声明的变量一定要在声明后使用,否则报错。

// var 的情况
console.log(foo); // 输出undefined
var foo = 2;

// let 的情况
console.log(bar); // 报错ReferenceError
let bar = 2;

2、let是一个块作用域(暂时性死区)

只要块级作用域内存在let命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响。例如:

var tmp = 123;

if (true) {
  tmp = 'abc'; // ReferenceError
  let tmp;
}

这是因为:

上面代码中,存在全局变量tmp,但是块级作用域内let又声明了一个局部变量tmp,导致后者绑定这个块级作用域,所以在let声明变量前,对tmp赋值会报错。

ES6 明确规定,如果区块中存在letconst命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。

总之,在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)

3、let不能重复声明

let不允许在相同作用域内,重复声明同一个变量

// 报错
function func() {
  let a = 10;
  var a = 1;
}

// 报错
function func() {
  let a = 10;
  let a = 1;
}

4、const

const也不能重复声明,也是块级作用域,也不存在变量提升,但是和let不同的是,const一旦声明,常量的值就不能改变。还有就是for循环不能用const。
比如我这么写,var和let都不会报错,但是const会报错:

const PI = 3.1415;
PI // 3.1415

PI = 3;
// TypeError: Assignment to constant variable.

对于const来说,只声明不赋值,就会报错:(const声明的变量不得改变值,这意味着,const一旦声明变量,就必须立即初始化,不能留到以后赋值)


const foo;
// SyntaxError: Missing initializer in const declaration

const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。

const foo = {};

// 为 foo 添加一个属性,可以成功
foo.prop = 123;
foo.prop // 123

// 将 foo 指向另一个对象,就会报错
foo = {}; // TypeError: "foo" is read-only

 上面代码中,常量foo储存的是一个地址,这个地址指向一个对象。不可变的只是这个地址,即不能把foo指向另一个地址,但对象本身是可变的,所以依然可以为其添加新属性。

作用1:for循环中用let比较合适

        for (var i = 0; i < 5; i++) {
                setTimeout(() => console.log(i), 1000)//5 5 5 5 5
            }
        for (let i = 0; i < 5; i++) {
                setTimeout(() => console.log(i), 1000)//0 1 2 3 4
            }

 上面的这个for循环,如果用var i = 0,那么最后结果是 5 5 5 5 5 ,这是因为用var是在全局只声明一个变量i,那么每一次循环,全局的i都会改变,里面的console.log(i)指向的都是同一个i,所以最后输出的是55555。

但是但是如果使用let声明就不一样了,i只在当前作用域生效,每一次循环都会重新声明变量。相当于以下代码::

for(let i = 0; i < 5; i++){
	let i = 当前i的值;
  	setTimeout(()=>console.log(i),1000)//0 1 2 3 4
}
也正是因为如此,循环中不能使用const,因为const定义后的变量不可修改,不能在上一轮循环的基础上进行计算(如++)再重新赋值给本轮声明。

作用2:for循环分为父作用域和子作用域

另外,for循环还有一个特别之处,就是设置循环变量的那部分是一个父作用域,而循环体内部是一个单独的子作用域。

for (let i = 0; i < 3; i++) {
  let i = 'abc';
  console.log(i);
}
// abc
// abc
// abc

上面代码正确运行,输出了 3 次abc。这表明函数内部的变量i与循环变量i不在同一个作用域,有各自单独的作用域(同一个作用域不可使用 let 重复声明同一个变量)。

作用3:let和const不会污染全局变量

        var RegExp = 100;
            console.log(RegExp);//100
            console.log(window.RegExp);//100

那么最后的结果是两个100,window对象直接被改了,但是如果用let

        let RegExp = 100;
        //const RegExp = 100;
            console.log(RegExp);//100
            console.log(window.RegExp);//function RegExp()

第一个是100,第二个结果还是window对象的RegExp函数。

综上所述:默认情况下用const,如果变量后面需要被修改再用let。

三、模板字符串

比如有一个div我想要js动态的往里面加这些东西,平常的写法是:

    <div></div>
    <script>
        const div = document.querySelector('div');
        let id='good';
        let name='写代码让我快乐';
        div.innerHTML = "<ul><li><p id="+id+">"+name+"</p></li></ul>";
    </script>

但是有了模板字符串之后:
使用tab上面那个小点点包起来,里面的变量使用${变量名}来替换,非常方便。

        const div = document.querySelector('div');
        let id='good';
        let name='写代码让我快乐';
        div.innerHTML= `<ul>
                <li>
                    <p id=${id}>${name}</p>
                </li>
            </ul>`
        // div.innerHTML = "<ul><li><p id="+id+">"+name+"</p></li></ul>";

 四、函数默认值、剩余参数、扩展运算符

1.函数默认值

如果我们想写一个求和函数,在es5中需要这样写:

function add(a, b) {
    a = a || 10;
    b = b || 20;
    return a + b;
}
console.log(add());

但是在es6中我们可以这样写:非常简单

function addd(a = 10, b = 20) {
    return a + b;
}
console.log(addd());
但是像下面这样写没有给b赋值会报错:
function addd(a = 10, b) {
    return a + b;
}
console.log(addd(20));

赋值也可以是函数比如:

function add2(a = 10, b = getVal(5)) {
    return a + b;
}

function getVal(val) {
    return val + 5;
}
console.log(add2());  //20

2.剩余参数

把一个对象儿中的属性名和属性值拿出来传给另一个对象。按照ES5的写法,如果不确定用户传几个参数,我们肯定是使用arguments伪数组来接收。

            function pick(obj) {
                let result = {};
                for (let i = 1; i < arguments.length; i++) {
                    result[arguments[i]] = obj[arguments[i]];
                }
                return result;
            }
            let book = {
                    title: '前端之王',
                    name: 'c',
                    age: 18
                }
            let bookData = pick(book, 'title', 'name', 'age');
            console.log(bookData);

但是在ES6中,我们可以获取一个真正的数组。使用(…+名字)这种语法来做形参,返回的是一个数组,但是这个一定要写在形参的最后一个。比如下面这个例子,obj对应book,args就是后面那三个属性名构成的数组

let book = {
    title: '前端之王',
    name: 'c',
    age: 18
}

function pick(obj, ...args) {
	console.log(args);  //['title', 'name', 'age']
    let result = {}; // 定义一个空对象
    for (let i = 0; i < args.length; i++) {
    //把对象中的属性值拿过来,给空对象(如果该对象没有这个属性,那么就添加一个)
        result[args[i]] = obj[args[i]];
    }
    return result;
}
let bookData = pick(book, 'title', 'name', 'age');
console.log(bookData);

有点迷糊在了解了解;

3.扩展运算符

扩展运算符:将一个数组(或对象)分割,并将数组的各个项作为分离的参数传给函数。其实就是把数组或对象拆开
1、比如我要获取数组的最大值,以前es5我们会使用apply

const arr = [123, 545, 34, 234, 5];
console.log(Math.max.apply(null, arr));

但是现在我们es6可以这样写 es6:

console.log(Math.max(...arr));

2、其实也可以吧对象里面的东西拆开:

let obj1 = {x:100, y:200};
let obj2 = {
    a:1,
    ...obj1,
    b:2
}
console.log(obj2);  //{a: 1, x: 100, y: 200, b: 2}

四、箭头函数

1.箭头函数的语法

在es6中,ES6 允许使用“箭头”(=>)定义函数,如果只有一个返回值,我们可以把return省略

var f = v => v;

// 等同于
var f = function (v) {
  return v;
};

如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分。 

var f = () => 5;
// 等同于
var f = function () { return 5 };

var sum = (num1, num2) => num1 + num2;
// 等同于
var sum = function(num1, num2) {
  return num1 + num2;
};

如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回。 

var sum = (num1, num2) => { return num1 + num2; }

由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号,否则会报错。

// 报错
let getTempItem = id => { id: id, name: "Temp" };

// 不报错
let getTempItem = id => ({ id: id, name: "Temp" });

2.对象中的函数和箭头函数

比如一个Person对象,里面有eat方法:

let person = {
    name: "jack",
    // 以前:
    eat: function (food) {
        console.log(this.name + "在吃" + food);
    },
    // 箭头函数版:
    eat2: food => console.log(person.name + "在吃" + food),// 这里拿不到this
    // 简写版:
    eat3(food){
        console.log(this.name + "在吃" + food);
    }
}

3.箭头函数的this指向

简单来说,想知道箭头函数中的this指向谁,就看这个箭头函数外边有没有包裹函数,如果它外面有函数,那么this指向的就是外层包裹函数的this,如果没有包裹函数,this指向的就是window

例如:

(1)例1

定义一个函数,定义一个对象,使用bind让函数中的this指向这个对象
这样的话,fn里的this指向的就是obj,返回值返回一个匿名箭头函数,箭头函数中的this指向的和包裹它的fn中的this指向相同,也是obj。
这里还要注意bind的特性,bind不会更改原函数,而是复制一份原函数生成一个新函数,新函数中this的指向改变,所以如果bind更改绑定后,调用fn的话,还是输出window

let obj = { name: 'c' };  //定义一个对象,让fn的this指向这个对象
function fn() {
    console.log(this);
    return () => {
        console.log(this);
    }
}
//更改fn函数的this指向
let newfn = fn.bind(obj);  //bind不会更改原函数,复制一份新的给newfn
fn();  //window
newfn(); //obj

(2) 

如果我定义一个对象,里面写个方法,方法里面的this指向的是函数的调用者,也就是Person这个对象,所以最后输出的结果是18

let Person = {
    name: 'c',
    age: 18,
    getAge: function () {
        console.log(this.age);
    }
}
Person.getAge();  //18

但是我如果把这个函数写成箭头函数,那么this就往外边查找,指向window

let Person = {
    name: 'zzy',
    age: 18,
    getAge: () => {
        console.log(this.age);
    }
}
Person.getAge();  //undefined

这里还有个注意点:对象有大括号,但不产生作用域 

(3) 

比如我定义一个Dj对象,里面有个事件监听,点击页面调用另一个方法
那么这么写会报错,因为this指向的是document,document里面根本没有dance方法

let Dj = {
    id: 007,
    drop: function () {
        document.addEventListener('click', function () {
        	console.log(this);  //document
            this.dance();
        })
    },
    dance: function () {
        console.log('drop the beat');
    }
}
Dj.drop();  //报错

但是如果我把监听事件里的函数写成箭头函数,this的绑定就消失了,此时this指向的是外层包裹函数的this,也就是drop函数里的this,也就是Dj对象,这样的话就可以找到dance方法了

let Dj = {
    id: 007,
    drop: function () {
        document.addEventListener('click', () => {
            console.log(this);  //Dj
            this.dance();
        })
    },
    dance: function () {
        console.log('drop the beat');
    }
}
Dj.drop();  //drop the beat

那如果我把drop也写成箭头函数呢?因为箭头函数没有this绑定,此时就会往上找,而对象的大括号没有作用域,所以this就会找到window,懂了吗

let Dj = {
    id: 007,
    drop: () => {
        document.addEventListener('click', () => {
            console.log(this);  //window
            this.dance();
        })
    },
    dance: function () {
        console.log('drop the beat');
    }
}
Dj.drop();  //报错

4.箭头函数的注意事项

1.使用箭头函数,函数内部没有arguments
2.箭头函数不能用来构造函数,因为function函数是一个对象,但是箭头函数不是一个对象,实际上箭头函数就是个语法糖。

(1)箭头函数没有自己的this对象(详见上文)。

(2)不可以当作构造函数,也就是说,不可以对箭头函数使用new命令,否则会抛出一个错误。

(3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。

(4)不可以使用yield命令,因此箭头函数不能用作 Generator 函数。

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值