《ES6 标准入门》读书笔记

来源:互联网 发布:进程调度算法代码 编辑:程序博客网 时间:2024/05/19 18:16

过年在家闲着没事,来看看ES6,选了阮一峰大大的《ES6 标准入门》这本书,了解一下新的js规范。这里做一下读书笔记。

ECMAScript 6 须知

目前各大浏览器的自新版本应该都支持ES6了,并且Node.js对ES6的支持度比浏览器还高,通过Node可以体验更多ES6的特性。

Babel转码器

Babel是个ES6转码器,可以将ES6代码转换为ES5代码,从而在现有环境执行。也就是说,你可以用ES6的方式编写程序,又不用担心现有环境是否支持。
还有个Traceur转码器也是一样的效果。

let和const命令

let命令

基本用法: let用来都声明变量,类似var,但是所生命的变量,只在let命令所在的代码块中有效。for循环计数器的i,就很适合用let命令,这样i只在for循环内有效。

var a = [];for (var i = 0; i < 10; i++) {a[i] = function () {console.log(i);};}a[6](); // 10var a = [];for (let i = 0; i < 10; i++) {a[i] = function () {console.log(i);};}a[6](); // 6

let不详var那样会发生“变量提升”现象,所以变量一定要先声明后使用,否则会报错。

变量提升简单来说,就是自动把变量的定义提前解析,这样,即使先使用,后定义变量,程序也可以正常运行。但是这里要注意,变量提升,只是提前声明了变量,并没有赋值。见下例:

console.log(foo); // 输出undefinedconsole.log(bar); // 报错ReferenceErrorvar foo = 2;let bar = 2;

var出来的foo变量提升了,但是提升只是定义,并不赋值,所以是undefined;而let bar 则不存在变量提升,会直接报错

关于变量/函数提升

函数只有声明形式才能提升。匿名函数赋值不能提升。

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

if (true) {// TDZ开始tmp = 'abc'; // ReferenceErrorconsole.log(tmp); // ReferenceErrorlet tmp; // TDZ结束console.log(tmp); // undefinedtmp = 123;console.log(tmp); // 123}

若在let声明变量前使用typeof,也会报错。完全不声明反而不会报错。

let不允许重复声明

ES6的块级作用域

let实际上为js新增了块级作用域。

function f1() {let n = 5;if (true) {let n = 10;}console.log(n); // 5}

上边的函数有两个代码块,都声明了n,运行后输出5,这说明外层代码块不受内层代码块的影响,如果这里用var,最后输出的n就是10。

const 命令

const 声明一个只读的常量,一旦声明,就要立即初始化复制,然后常量的值不能改变。

const的作用域跟let相同: 只在声明所在的块级作用域内有效。并且没有变量提升,一定要先声明后使用。且不可重复声明

const声明的对象,只是指向对象的地址不变,对象本身是可变的。但不能重新赋值
const声明的数组,可以用push等方法,但是不能重新赋值

从ES6开始,全局变量将逐步与全局对象的属性脱钩。let,const,class声明的全局变量,不再属于全局对象的属性:

var a = 1window.a // 1let b = 1;window.b // undefined

变量的解构赋值

ES6允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构。

如:

// ES5 赋值var a = 1;var b = 2;var c = 3;//ES6 解构  赋值var [a,b,c] = [1,2,3];//只要等号两边模式相同,左边的变量就会被赋予对应的值。

若解构不成功,变量的值就等于undefined

其实还有不完全解构:

let [x, y] = [1, 2, 3];x // 1y // 2let [a, [b], d] = [1, [2, 3], 4];a // 1b // 2d // 4

对象的解构,要变量名相同:

var { bar, foo } = { foo: "aaa", bar: "bbb" };foo // "aaa"bar // "bbb"var { baz } = { foo: "aaa", bar: "bbb" };baz // undefined

字符串解构:

const [a, b, c, d, e] = 'hello';//a-e分别是 h,e,l,l,o

数值/布尔值解构:

let {toString: s} = 123;s === Number.prototype.toString // truelet {toString: s} = true;s === Boolean.prototype.toString // true

函数参数解构:

function add([x, y]){return x + y;}add([1, 2]); // 3

解构的用途:
1.交换变量的值: [x,y] = [y,x]
2.函数返回多个值。
3.函数参数的定义
4.提取json数据:

var jsonData = {id: 42,status: "OK",data: [867, 5309]};let { id, status, data: number } = jsonData;console.log(id, status, number);// 42, "OK", [867, 5309]

字符串的扩展

includes():返回布尔值,表示是否找到了参数字符串。
startsWith():返回布尔值,表示参数字符串是否在源字符串的头部。
endsWith():返回布尔值,表示参数字符串是否在源字符串的尾部。
repeat() : 返回一个新字符串,表示将原字符串重复n次。
padStart() : 从头部补全字符串
padEnd() : 从尾部补全字符串

数值的扩展

Number.isFinite()用来检查一个数值是否为有限的(finite)。
Number.isNaN()用来检查一个值是否为NaN。
ES6将全局方法parseInt()和parseFloat(),移植到Number对象上面,行为完全保持不变。Number.parseInt(), Number.parseFloat()
Number.isInteger()用来判断一个值是否为整数。

ES6在Number对象上面,新增一个极小的常量Number.EPSILON。为浮点数计算设置误差范围,因为我们知道js浮点数计算是不精确的。但是如果这个误差能够小于Number.EPSILON,我们就可以认为得到了正确结果。因此,Number.EPSILON的实质是一个可以接受的误差范围。

Number.isSafeInteger()则是用来判断一个整数是否落在-2^53到2^53之间(不含两个端点),超过这个范围,无法精确表示这个值。

Math对象的扩展

Math.trunc方法用于去除一个数的小数部分,返回整数部分。对于非数值,Math.trunc内部使用Number方法将其先转为数值。

Math.sign方法用来判断一个数到底是正数、负数、还是零。

Math.cbrt方法用于计算一个数的立方根。对于非数值,Math.cbrt方法内部也是先使用Number方法将其转为数值。

Math.hypot方法返回所有参数的平方和的平方根。如果参数不是数值,Math.hypot方法会将其转为数值。只要有一个参数无法转为数值,就会返回NaN。

对数方法若干。
三角函数方法若干。

新增指数运算符:
ES7新增了一个指数运算符(**),目前Babel转码器已经支持。2 ** 3 // 8

数组的扩展

Array.from方法用于将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象(包括ES6新增的数据结构Set和
Map)。实际应用中,常见的类似数组的对象是DOM操作返回的NodeList集合,以及函数内部的arguments对象。Array.from都可以将它们转为真正的数组。

// NodeList对象let ps = document.querySelectorAll('p');Array.from(ps).forEach(function (p) {console.log(p);});// arguments对象function foo() {var args = Array.from(arguments);// ...}

上面代码中,querySelectorAll方法返回的是一个类似数组的对象,只有将这个对象转为真正的数组,才能使用forEach方法。

Array.of方法用于将一组值,转换为数组。

Array.of(3, 11, 8) // [3,11,8]Array.of(3) // [3]Array.of(3).length // 1

数组实例的copyWithin方法,在当前数组内部,将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组。也就是说,使用这个方
法,会修改当前数组。
Array.prototype.copyWithin(target, start = 0, end = this.length)
它接受三个参数。
target(必需):从该位置开始替换数据。
start(可选):从该位置开始读取数据,默认为0。如果为负值,表示倒数。
end(可选):到该位置前停止读取数据,默认等于数组长度。如果为负值,表示倒数。
这三个参数都应该是数值,如果不是,会自动转为数值。

[1, 2, 3, 4, 5].copyWithin(0, 3)// [4, 5, 3, 4, 5]//上面代码表示将从3号位直到数组结束的成员(4和5),复制到从0号位开始的位置,结果覆盖了原来的1和2。

数组实例的find方法,用于找出第一个符合条件的数组成员。它的参数是一个回调函数,所有数组成员依次执行该回调函数,直到找出第一个返回值
为true的成员,然后返回该成员。如果没有符合条件的成员,则返回undefined。

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

fill方法使用给定值,填充一个数组。fill方法还可以接受第二个和第三个参数,用于指定填充的起始位置和结束位置。

['a', 'b', 'c'].fill(7, 1, 2)// ['a', 7, 'c']//上面代码表示,fill方法从1号位开始,向原数组填充7,到2号位之前结束。

ES6提供三个新的方法——entries(),keys()和values()——用于遍历数组。它们都返回一个遍历器对象(详见《Iterator》一章),可以
用for…of循环进行遍历,唯一的区别是keys()是对键名的遍历、values()是对键值的遍历,entries()是对键值对的遍历。

Array.prototype.includes方法返回一个布尔值,表示某个数组是否包含给定的值,与字符串的includes方法类似。该方法属于ES7,但Babel转码器
已经支持。没有该方法之前,我们通常使用数组的indexOf方法,检查是否包含某个值。但indexof不够语义化,而且内部使用===容易导致误判

数组的空位指,数组的某一个位置没有任何值。比如,Array构造函数返回的数组都是空位。Array(3) // [, , ,] ,Array(3)返回一个具有3个空位的数组。
ES6则是明确将空位转为undefined。
Array.from方法会将数组的空位,转为undefined,也就是说,这个方法不会忽略空位。

函数的扩展

在ES6之前,不能直接为函数的参数指定默认值,只能采用变通的方法。如:

function log(x, y) {y = y || 'World';console.log(x, y);}

ES6允许为函数的参数设置默认值,即直接写在参数定义的后面。

function log(x, y = 'World') {console.log(x, y);}log('Hello') // Hello Worldlog('Hello', 'China') // Hello Chinalog('Hello', '') // Hello

ES6引入rest参数(形式为“…变量名”),用于获取函数的多余参数,这样就不需要使用arguments对象了。rest参数搭配的变量是一个数组,该变量将
多余的参数放入数组中。

function add(...values) {    let sum = 0;    for (var val of values) {        sum += val;    }    return sum;}add(2, 5, 3) // 10

扩展运算符

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

function push(array, ...items) {    array.push(...items);}function add(x, y) {    return x + y;}var numbers = [4, 38];add(...numbers) // 42//上面代码中,array.push(...items)和add(...numbers)这两行,都是函数的调用,它们的都使用了扩展运算符。该运算符将一个数组,变为参数序列。

扩展运算符的应用:

//合并数组新方法:// ES5[1, 2].concat(more)// ES6[1, 2, ...more]//字符串转数组:[...'hello']// [ "h", "e", "l", "l", "o" ]

函数的name属性,返回函数的函数名。

箭头函数

ES6允许使用“箭头”(=>)定义函数。 var 函数名 = (参数) => 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;};

箭头函数可以与变量解构结合使用。

const full = ({ first, last }) => first + ' ' + last;// 等同于function full(person) {return person.first + ' ' + person.last;}

箭头函数使得表达更加简洁。

const isEven = n => n % 2 == 0;const square = n => n * n;//上面代码只用了两行,就定义了两个简单的工具函数。如果不用箭头函数,可能就要占用多行,而且还不如现在这样写醒目。

使用箭头函数的注意点:
(1)函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
(2)不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
(3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用Rest参数代替。
(4)不可以使用yield命令,因此箭头函数不能用作Generator函数。

尾调用(Tail Call)是函数式编程的一个重要概念,本身非常简单,一句话就能说清楚,就是指某个函数的最后一步是调用另一个函数。
函数调用自身,称为递归。如果尾调用自身,就称为尾递归。递归非常耗费内存,因为需要同时保存成千上百个调用帧,很容易发生“栈溢出”错误(stack overflow)。但对于尾递归来说,由于只存在一个调用
帧,所以永远不会发生“栈溢出”错误。

对象的扩展

ES6允许直接写入变量和函数,作为对象的属性和方法。这样的书写更加简洁。

var foo = 'bar';var baz = {foo};baz // {foo: "bar"}// 等同于var baz = {foo: foo};//上面代码表明,ES6允许在对象之中,只写属性名,不写属性值。这时,属性值等于属性名所代表的变量。function f(x, y) {    return {x, y};}// 等同于function f(x, y) {    return {x: x, y: y};}f(1, 2) // Object {x: 1, y: 2}//除了属性简写,方法也可以简写:var o = {    method() {        return "Hello!";    }};// 等同于var o = {    method: function() {        return "Hello!";    }};

CommonJS模块输出变量,就非常合适使用简洁写法。

var ms = {};function getItem (key) {return key in ms ? ms[key] : null;}function setItem (key, value) {ms[key] = value;}function clear () {ms = {};}module.exports = { getItem, setItem, clear };// 等同于module.exports = {getItem: getItem,setItem: setItem,clear:

ES5中,只有方括号法才能放表达式,ES6允许在字面量定义对象时,用表达式作为对象的属性名:

let propKey = 'foo';let obj = {    [propKey]: true,    ['a' + 'bc']: 123};obj.foo // trueobj.abc // 123

表达式还可以用于定义方法名:

let obj = {    ['h'+'ello']() {        return 'hi';    }};obj.hello() // hi

函数的name属性,返回函数名。对象方法也是函数,因此也有name属性。

ES5比较两个值是否相等,只有两个运算符:相等运算符(==)和严格相等运算符(===)。它们都有缺点,前者会自动转换数据类型,后者的NaN不
等于自身,以及+0等于-0。JavaScript缺乏一种运算,在所有环境中,只要两个值是一样的,它们就应该相等。
ES6提出“Same-value equality”(同值相等)算法,用来解决这个问题。Object.is就是部署这个算法的新方法。它用来比较两个值是否严格相等,与
严格比较运算符(===)的行为基本一致。

Object.assign方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。

var target = { a: 1 };var source1 = { b: 2 };var source2 = { c: 3 };Object.assign(target, source1, source2);target // {a:1, b:2, c:3}

如果只有一个参数,Object.assign会直接返回该参数。如果该参数不是对象,则会先转成对象,然后返回。由于undefined和null无法转成对象,所以如果它们作为参数,就会报错。
注意,Object.assign方法实行的是浅拷贝,而不是深拷贝。也就是说,如果源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用。

扩展运算符(…)用于取出参数对象的所有可遍历属性,拷贝到当前对象之中。

let z = { a: 3, b: 4 };let n = { ...z };n // { a: 3, b: 4 }

这等同于使用Object.assign方法。

Symbol

ES5的对象属性名都是字符串,这容易造成属性名的冲突。比如,你使用了一个他人提供的对象,但又想为这个对象添加新的方法(mixin模式),新
方法的名字就有可能与现有方法产生冲突。如果有一种机制,保证每个属性的名字都是独一无二的就好了,这样就从根本上防止属性名的冲突。这就
是ES6引入Symbol的原因。
ES6引入了一种新的原始数据类型Symbol,表示独一无二的值。它是JavaScript语言的第七种数据类型,前六种是:Undefined、Null、布尔值
(Boolean)、字符串(String)、数值(Number)、对象(Object)。

P107

0 0
原创粉丝点击