资讯专栏INFORMATION COLUMN

ES6(上中)

winterdawn / 281人阅读

摘要:正式因为它没有,所以也就不能用作构造函数。函数的最后一步是调用函数,这就叫尾调用尾递归函数调用自身,称为递归。它的参数是一个回调函数,所有数组成员依次执行该回调函数,直到找出第一个返回值为的成员,然后返回该成员。

这是ES6的入门篇教程的笔记,网址:链接描述,以下内容中粗体+斜体表示大标题,粗体是小标题,还有一些重点;斜体表示对于自身,还需要下功夫学习的内容。这里面有一些自己的见解,所以若是发现问题,欢迎指出~
上一篇es5的到最后令人崩溃,看来深层的东西还是不太熟,希望这次不要这样了!!!

函数的扩展

1、函数参数的默认值
基本用法
ES6之前,不能直接为函数的参数指定默认值,只能采用变通的方法。
参数默认值不是传值的,而是每次都重新计算默认值表达式的值。也就是说,参数默认值是惰性求值的。

function log(x, y) {
    y = y || "World";
    console.log(x, y);
}
log("Hello", "") // Hello World 后一个字段是本意是空字符,也会被赋值为"World"

// 改进一下,赋值时为以下
if (typeof y === "undefined") {
    y = "World";
}

// ES6允许为函数的参数设置默认值,即直接写在参数定义的后面
function log(x, y = "World") {
    console.log(x, y);
}
log("Hello") // Hello World
log("Hello", "China") // Hello World
log("Hello", "") // Hello

let x = 99;
function foo(p = x + 1) {
    console.log(p);
}
foo() // 100
x = 100;
foo() // 101 这就是惰性求值,参数p的默认值是x+1,每次调用函数foo,都会重新计算x + 1,而不是默认p等于100

与解构赋值默认值结合使用
参数默认值可以与解构赋值的默认值,结合起来使用。

function foo({x, y = 5}) { // 只用了对象的结构赋值默认值,没有使用函数参数的默认值。
    console.log(x, y);
}
foo({}) // undefined 5
foo() // TypeError: Cannot read prototype "x" of undefined

function foo({x, y = 5} = {}) { // 如果没有提供参数,函数foo的参数默认为一个空对象。
    console.log(x, y);
}
foo() // undefined 5

有点要绕晕了,下面这个是重点,如果能知道两者的区别,说明就已经理解了。

// 第一种
function m1({x = 0, y = 0} = {}) {
    return [x, y];
}
// 第二种
function m2({x, y} = {x: 0, y: 0}) {
    return [x, y];
}

上面的这两种写法都对函数的参数设定了默认值,区别在于第一种行数参数的默认值是空对象,但是这是了对象解构赋值的默认值;第二种函数参数的默认值是一个有具体属性的对象,但是没有设置对象解构赋值的默认值。
一起看看它们的输出情况。

// 函数没有参数的情况
m1() // [0, 0]
m2() // [0, 0]

// x有值,y无值的情况
m1({x: 3}) // [3, 0]
m2({x: 3}) // [3, undefined]

// x和y都无值的情况
m1({}) // [0, 0]
m2({}) // [undefined, undefined]

// 综上,推荐第一种写法,当然按需写更好

参数默认值的位置
通常情况下,定义了默认值的参数,应该是函数的尾参数。因为这样比较容易看出来,到底省略了那些参数。如果非尾部的参数设置默认值,实际上这个参数是没法省略的,除非显式输入undefined。

//
function f(x = 1, y) {
    return [x, y];
}
f() // [1, undefined]
f(2) // [2, undefined]
f(, 1) // 报错
f(undefined, 1) // [1, 1]

作用域
一旦设置了参数的默认值,函数进行声明初始化时,参数会形成一个多带带的作用域(context)。等到初始化结束,这个作用域就会消失。这种语法行为,再不设置参数默认值时,是不会出现的。

let x = 1;
function f(y = x) { // 参数y = x形成一个多带带的作用域,在这个作用域里面,变量x本身没有定义,所以指向外层的全局变量x
    let x = 2; // 函数调用时,函数体内部的局部变量x影响不到默认值变量x
    console.log(y)
}
f() // 1

// 如果此时,全局变量x不存在,就会报错
function f(y = x) {
    let x = 2;
    console.log(y);
}
f() // ReferenceError: x is not defined

2、rest参数
ES6引入rest参数(形式为...变量名),用于获取函数的多余参数,这样就不需要arguments对象了。rest参数搭配的变量时一个数组,该变量将多余的参数放入数组中。
注:rest参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。
函数的length属性,不包括rest参数。

function add(...values) { // 利用rest参数,可以向该函数传入任意数目的参数
    let sum = 0;
    for(let val of values) {
        sum += val;
    }
    return sum;
}
add(2, 5, 3) // 10

// 利用rest参数改写数组push方法
function push(array, ...items) {
    items.forEach(function(item) {
        array.push(item);
        console.log(item);
    })
}
let a = [];
push(a, 1, 2, 3);

// rest参数之后不能再有其他参数
// 报错
function f(a, ...b, c) {
    // ...
}

5、箭头函数
基本用法
ES6允许使用“箭头”(=>)定义函数。
如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分。
如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回。
注:函数体内的this对象,就是定义时所在的对象而不是使用时所在的对象(this对象的指向是可变的,但是在箭头函数中,它是固定的);不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。

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

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

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

// 报错 返回对象必须在对象外面加上括号,否则会报错。
let getItem = id => { id: id, name: "Temp" };
// 不报错
let getItem = id => ({ id: id, name: "Temp" });

// 箭头函数可以与变量解构结合使用
const full = ({ first, last }) => first + "" + last;

// 箭头函数使得表达更加简介
const isEven = n => n % 2 === 0; 
const aqure = n => n * n;

// 简化回调函数
let result = values.sort((a, b) => a-b);
// 正常写法
let result = values.sort(function (a, b) {
    return a - b;
});

需要注意this的指向问题:箭头函数让this指向固定化,箭头函数的this绑定定义时错在的作用域,普通函数的this指向运行时所在的作用域。

let handle = {
    id: "123456",
    init: function() {
        document.addEventListener("click",
            event => this.doSomething(event.type), false); // 箭头函数,this.doSomething中的this指向handler对象(定义时的作用域);否则的话,this指向document对象
    },
    doSomething: function(type) {
        console.log("Handling " + type + " for " + this.id);
    }
};

this指向的固定化,并不是因为箭头函数内部有绑定this的机制,实际原因是箭头函数根本没有自己的this,导致内部的this就是外层代码块的this。正式因为它没有this,所以也就不能用作构造函数。

// ES6
function foo() {
    setTimeout(() => {
        console.log("id:", this.id);
    }, 100);
}

// ES5
function foo() {
    let _this = this;
    setTimeout(function () {
        console.log("id:", _this.id);
    }, 100);
}

箭头函数不适用场合
1、定义对象的方法,且该方法内部包括this。
这是因为对象不构成多带带的作用域,导致箭头函数定义时的对象就是全局作用域。

const cat = {
    lives: 9,
    jumps: () => { // 箭头函数,使得this指向全局对象,不会得到预期解构;如果是普通函数,该方法内部的this指向cat
        this.lives--;
    }
}

2、需要动态this的时候,也不应使用箭头函数。

let button = document.getElementById("press");
button.addEventListener("click", () => {
    this.classList.toggle("on");
});

点击按钮会报错,因为button的监听函数是一个箭头函数,导致里面的this就是全局对象。如果改成普通函数,this就会动态指向被点击的按钮对象。
另外,如果函数体很复杂,有很多行,或者函数内部有大量的读写操作,不单纯是为了计算值,这时也不应该使用箭头函数,而是要使用普通函数,这样可以提高代码可读性。

6、尾调用优化
尾调用(Tail Call)是函数式编程的一个重要概念,本身非常简单,一句话就能说清楚,就是指某个函数的最后一步是调用另一个函数。

function f(x) {
    return g(x); // 函数f的最后一步是调用函数g,这就叫尾调用
}

尾递归
函数调用自身,称为递归。如果尾调用自身,就称为尾递归。

数组的扩展

含义
扩展运算符(spread)是三个点(...)。它好比rest参数的逆运算,将一个数组转为用逗号分隔的参数序列。

console.log(1, ...[2, 3, 4], 5);
// 1 2 3 4 5
// 如果扩展运算符后面是一个空数组,则不产生任何效果
[...[], 1]
// [1]

// 注意,只有函数调用时,扩展运算符才可以放在圆括号中,否则会报错。前两种报错,是因为扩展运算符所在的括号不是函数调用。
(...[1, 2]) // Uncaught SyntaxError: Unexpected token ...
console.log((...[1, 2])) // Uncaught SynaxError: Unexpexted token ...
console.log(...[1, 2]) // 1 2

扩展运算符的应用
(1)复制数组
数组是复合的数据类型,直接复制的话,指数复制了指向底层数据结构的指针,而不是克隆一个全新的数组。

const a1 = [1, 2];
const a2 = a1; // a2并不是a1的克隆,而是指向同一份数据的另一个指针,修改a2,会直接导致a1的改变。
a2[0] = 2;
a1; // [2, 2]

// ES5复制数组
const a1 = [1, 2];
const a2 = a1.concat();
a2[0] = 2;
a1 // [1, 2]

// ES6的简便写法
const a1 = [1, 2];
const a2 = [...a1];
const [...a2] = a1; // 这两种写法,a2都是a1的克隆

(2)合并数组
扩展运算符提供了数组合并的新写法。

const arr1 = ["a", "b"];
const arr2 = ["c"];
// ES5的合并数组
arr1.concat(arr1, arr2);
// ES6的合并数组
[...arr1, ...arr2];

// 注意下面的合并,数组里面的元素是对象,拷贝过去的就只能是地址!!!
const a1 = [{ foo: 1 }];
const a2 = [{ bar: 2 }];
const a3 = a1.concat(a2);
const a4 = [...a1, ... a2];
a3[0] === a1[0]; // true
a4[0] === a1[0]; // true
// 拷贝过去的只有地址,也就是说,如果修改了原数组的成员,会同步反映到新数组。

(3)与解构赋值结合
扩展运算符可以与解构赋值结合起来,用于生成数组。

// ES5
a = list[0], rest = list.slice(1)
// ES6
[a, ...rest] = list

// 如果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错
// 报错
const [...butLast, last] = [1, 2, 3, 4, 5];

(4)字符串
扩展运算符还可以将字符串转为真正的数组。

[..."hello"]; // ["h", "e", "l", "l", "o"]

5、数组实例的find()和findIndex()
数组实例的find方法,用于找出第一个符合条件的数组成员。它的参数是一个回调函数,所有数组成员依次执行该回调函数,直到找出第一个返回值为true的成员,然后返回该成员。如果没有符合条件的成员,则返回undefined。接受三个参数,依次为当前的值、当前的位置和原数组。

[1, 4, -5, 10].find((n) => n < 0) // -5

数组实例的findIndex方法的用法与find方法非常类似,返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1.

7、数组实例的entries(),keys()和values()
ES6提供三个新的方法——entries(), keys()和values()——用于遍历数组。keys()是对键名的遍历、values()是对键值的遍历,entries()是对键值对的遍历。
感觉可以用foreach一步做到,没必要细看。。。。

8、数组实例的includes()
Array.prototype.includes方法返回一个布尔值,表示某个数组是否包含给定的值,与字符串的includes方法类似。ES2016引入了该方法。

[1, 2, 3].includes(2) // true
[1, 2, 3].includes(4) // false
[1, 2, NaN].includes(NaN) // true

[1, 2, 3].includes(3, 3); // false 第二个参数表示搜索的起始位置
[1, 2, 3].includes(3, -1); //true

9、数组实例的flat(),flatMap()
数组的成员有时还是数组,Array.prototype.flat()用于将嵌套的数组“拉平”,变成一维的数组。该方法返回一个新数组,对原数据没有影响。

[1, 2, [3, [4, 5]]].flat(); // [1, 2, 3, [4, 5]] 默认只会“拉平”一层
[1, 2, , [3, [4, 5]]].flat(2); // [1, 2, 3, 4, 5] 参数为2,表示要“拉平”两层的嵌套数组,会跳过空位
[1, [2, [3]]].flat(Infinity); // [1, 2, 3]  Infinity关键字作为参数,不管有多少层嵌套,都要转成一维数组

flatMap()方法对原数组的每个成员执行一个函数(相当于执行Array.prototype.map()),然后回返回值组成的数组执行flat()方法(默认只能展开一层)。该方法返回一个新数组,不改变原数组。

[1, 2, 3, 4].flatMap(x => [[x * 2]]) // [[2], [4], [6], [8]] 相当于[[[2]], [[4]], [[6]], [[8]]].flat()

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/105574.html

相关文章

  • css上中下布局自适应中间,左右布局自适应左侧display: table

    摘要:在我们经常用到固定头部和底部,自适应中间部分,或者固定左侧,自适应右侧等。在网上看了很多方法,一般都是通过绝对定位完成,具体可以网上去搜,这样可以完成上中下的布局,但是这次基础上再做左右布局浮动会出现问题,具体什么问题我没有深究。 在css我们经常用到固定头部和底部,自适应中间部分,或者固定左侧,自适应右侧等。在网上看了很多方法,一般都是通过绝对定位完成,position: absol...

    Baaaan 评论0 收藏0
  • 前端web网站上中(左右)下布局(flex、calc)

    摘要:基本布局上中左右下布局头部左侧超过高度出现滚动条超过高度出现滚动条超过高度出现滚动条超过高度出现滚动条超过高度出现滚动条超过高度出现滚动条超过高度出现滚动条超过高度出现滚动条超过高度出现滚动条超过高度出现滚动条超过高度出 基本布局1: 上中(左右)下布局 html,body{ margin:0; height:100%; ove...

    王晗 评论0 收藏0
  • 前端web网站上中(左右)下布局(flex、calc)

    摘要:基本布局上中左右下布局头部左侧超过高度出现滚动条超过高度出现滚动条超过高度出现滚动条超过高度出现滚动条超过高度出现滚动条超过高度出现滚动条超过高度出现滚动条超过高度出现滚动条超过高度出现滚动条超过高度出现滚动条超过高度出 基本布局1: 上中(左右)下布局 html,body{ margin:0; height:100%; ove...

    sourcenode 评论0 收藏0
  • 设计模式之蝉——代理模式上中

    摘要:代理模式的扩展普通代理这种代理就是客户端只能访问代理角色,而不能访问真实角色。与设计模式之蝉代理模式上片基本差不多。 代理模式的扩展 1 普通代理 :这种代理就是客户端只能访问代理角色,而不能访问真实角色。与设计模式之蝉——代理模式上 片基本差不多。(1)Subject抽象主题角色: showImg(https://segmentfault.com/img/bVbh8S5?w=1954...

    lyning 评论0 收藏0
  • 《你不知道的JavaScript》 (下) 阅读摘要

    摘要:本书属于基础类书籍,会有比较多的基础知识,所以这里仅记录平常不怎么容易注意到的知识点,不会全记,供大家和自己翻阅不错,下册的知识点就这么少,非常不推介看下册上中下三本的读书笔记你不知道的上读书笔记你不知道的中读书笔记你不知道的下读书笔记第三 本书属于基础类书籍,会有比较多的基础知识,所以这里仅记录平常不怎么容易注意到的知识点,不会全记,供大家和自己翻阅; 不错,下册的知识点就这么少,非...

    Jacendfeng 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<