ES6(上中)

2019-07-12 admin

这是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://segmentfault.com/a/1190000019743944

本站文章除注明转载外,均为本站原创或编译。欢迎任何形式的转载,但请务必注明出处。

转载请注明:文章转载自 JavaScript中文网 [https://www.javascriptcn.com]

本文地址:https://www.javascriptcn.com/read-69572.html

文章标题:ES6(上中)

相关文章
JavaScript教程:JS中的原型
Keith Peters 几年前发表的一篇博文,关于学习没有“new”的世界,其中解释了使用原型继承代替构造函数。两者都是纯粹的原型编码。 标准方法(The Standard Way) 一直以来,我们学习的在 JavaScript 里创建对...
2015-11-12
JS中的语音合成——Speech Synthesis API
JS中的语音合成——Speech Synthesis API 简介 HTML5中和Web Speech相关的API实际上有两类,一类是“语音识别(Speech Recognition)”,另外一个就是“语音合成(Speech Synthes...
2018-05-17
canvas图片绘制跨域问题解决方案Tainted canvases may not be exported
图片跨域问题的一般解决方法 当使用canvas绘制网络图片的时候,经常会出现“Tainted canvases may not be exported”报错,上网搜一下解决方案,应该给的都是给img添加crossOrigin属性,尝试了一下...
2018-04-19
梳理前端开发使用eslint-prettier检查和格式化代码
问题痛点 在团队的项目开发过程中,代码维护所占的时间比重往往大于新功能的开发。因此编写符合团队编码规范的代码是至关重要的,这样做不仅可以很大程度地避免基本语法错误,也保证了代码的可读性。 对于代码版本管理系统(svn 和 git或者其他)...
2018-05-07
js实现手机拍照上传功能
在前段时间的项目开发中,用到了拍照上传的地方,后来发现了最为简单的一种方法,现总结如下: &lt;form id=&quot;form&quot; method=&quot;post&quot; action=&quot;http:&#x2...
2017-03-06
jQuery中DOM树操作之使用反向插入方法实例分析
本文实例讲述了jQuery中DOM树操作之使用反向插入方法。分享给大家供大家参考。具体分析如下: 使用反向插入方法 这里我们先把创建的内容插人到元素前面,然后再把同一个元素插人到文档 中的另一个位置。通常,当在jQuery中操作元素时,利用...
2015-11-13
可以从CSS框架中借鉴到什么
现在很多人会使用 CSS 框架进行快速建站。   那 CSS 框架是什么呢,它通常是一些 CSS 文件的集合,这些文件包括基本布局、表单样式、网格、简单组件、以及样式重置。使用 CSS 框架大大降低工作成本进行快速建站。   当然对于一些大...
2016-03-11
vue-awesome-swiper的使用以及API整理
一、先说一个看关于vue-awesome-swiper的一个坑 vue项目的package.json中显示的&lt;span style=“color: orange;”&gt;“vue-awesome-swiper”: “^2.5.4”&...
2018-04-26
jQuery中DOM树操作之复制元素的方法
本文实例讲述了jQuery中DOM树操作之复制元素的方法。分享给大家供大家参考。具体分析如下: 复制元素 前面提到的操作包括:插人新创建的元素、将元素从文档中的一个位置移动 到另一个位置,以及通过新元素来包装已有的元素。可是,有时候也会用到...
2015-11-13
从 JavaScript 到 TypeScript - 声明类型
从 JavaScript 语法改写为 TypeScript 语法,有两个关键点,一点是类成员变量 (Field) 需要声明,另一点是要为各种东西 (变量、参数、函数 / 方法等) 声明类型。而这两个点直接引出了两个关键性的问题,有哪些类型?...
2017-06-05
回到顶部