这是 ES6 的入门篇教程的笔记,网址:链接描述,以下内容中 粗体 + 斜体 表示大标题,粗体 是小标题,还有一些重点;斜体 表示对于自身,还需要下功夫学习的内容。这里面有一些自己的见解,所以若是发现问题,欢迎指出~
上一篇 es5 的到最后令人崩溃,看来深层的东西还是不太熟,希望这次不要这样了!!!
对象的扩展
1、属性的简洁表示法
ES6 允许直接写入变量和函数,作为对象的属性和方法,这样的书写更加简洁。
const foo = 'bar';
const baz = {foo};
baz // {foo: "bar"}
// 等同于
const baz = {foo: foo}; // 在对象之中,直接写变量,这时,属性名为变量名,属性值为变量值
除了属性简写,方法也可以简写。
const o = {method() {return "Hello!";}
};
// 等同于
const o = {method: function() {return "Hello!";}
};
// 一个实际的例子
let birth = '2000/01/01';
const Person = {
name: '张三',
birth, // 等同于 birth: birth
hello() { console.log('我的名字是', this.name); } // 等同于 hello: function ()...};
module.exports = {getItem, setItem, clear};
// 等同于
module.exports = {
getItem: getItem,
setItem: setItem,
clear: clear
};
2、属性名表达式
JavaScript 定义对象的属性,有两种方法。
obj.foo = true; // 直接用标识符作为属性名
obj['a' + 'bc'] = 123; // 用表达式作为属性名,这时要将表达式放在方括号之内
// ES6 允许字面量定义对象时,用表达式作为对象的属性名,即把表达式放在 ** 方括号 ** 内
let propKey = 'foo';
let obj = {[propKey]: true,
['a' + 'bc']: 123
};
需要注意的是,属性名表达式与简洁表示法,不能同时使用,会报错。
// 报错
const foo = 'bar';
const bar = 'abc';
const baz = {[foo] };
// 正确
const foo = 'bar';
const baz = {[foo]: 'abc' };
// 还需要注意的是,属性名表达式如果是一个对象,默认情况下会自动将对象转为字符串[object Object],这一点需要特别的小心
属性的遍历
ES6 一共有 5 种方法可以遍历对象的属性。
(1)for…in
for…in 循环遍历对象自身和继承的可枚举属性(不含 Symbol 属性)。
(2)Object.keys(obj)
Object.keys 返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)的键名。
(3)Object.getOwnPropertyNames(obj)
Object.getOwnPropertyNames(obj) 返回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)的键名。
(4)Object.getOwnPropertySymbols(obj)
Object.getOwnPropertySymbols 返回一个数组,包含对象自身的所有 Symbol 属性的键名。
(5)Reflect.ownKeys(obj)
Reflect.ownKeys 返回一个数组,包含对象自身的所有键名,不管 Symbol 或字符串,也不管是否可枚举。
扩展运算符
如果扩展运算符后面是一个空对象,则没有任何效果。
{...{}, a: 1} // {a: 1}
// 如果扩展运算符后面不是对象,则会自动将其转为对象
{...1} // {}
// 数组是特殊的对象,所以对象的扩展运算符也可以用于数组
let foo = {...['a', 'b', 'c'] };
foo // {0: "a", 1: "b", 2: "c"}
对象的扩展运算符等同于使用 Object.assign()方法。(Object.assign 方法只对顶层属性赋值,并没有继续做递归,所以它并不是真正的深拷贝。)
let aClone= {...a};
// 等同于
let aClone = Object.assign({}, a);
对象的新增方法
1、Object.is()
ES5 比较两个值是否相等,只有两个运算符:相等运算符(==)和严格相等运算符(===)。两者都有缺点,(==)会自动转换数据类型,(===)的 NaN 不等于自身,以及 + 0 等于 -0。JavaScript 缺乏一种运算,在所有环境中,只要两个值是一样的,它们就应该相等。
ES6 提出“Same-value equality”(同值相等)算法,用来解决这个问题。
Object.is 就是部署这个算法的新方法。它用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致。
Object.is('foo', 'foo'); // true
Object.is({}, {}); // false 两个对象的地址不一样
// 与严格比较运算符的不同之处只有两个:一是 + 0 不等于 -0,二是 NaN 等于自身
+0 === -0 // true
NaN === NaN // false
Object.is(+0, -0); // false
Object.is(NaN, NaN); // true
2、Object.assign()
基本用法
Object.assign 方法用于对象的合并,将源对象(source)的所有可枚举属性,赋值到目标对象(target)。
Object.assign 方法的第一个参数是目标对象,后面的参数都是源对象。
const target = {a: 1, b: 1};
const source1 = {b: 2, c: 2}; // 目标对象与源对象有同名属性 b
const source2 = {c: 3}; // 多个源对象有同名属性
Object.assign(target, source1, source2);
target; // {a: 1, b: 2, c: 3} 后面的属性会覆盖前面的属性
// 如果参数不是对象,则会先转成对象,然后返回。// 由于 undefined 和 null 无法转成对象,所以如果它们作为参数,就会报错。Object.assign(undefined); // 报错
Object.assign(null); // 报错
// 如果非对象参数出现在源对象的位置(即非首参数),那么处理规则有所不同。首先,这些参数都会转成对象,如果无法转成对象,就会跳过。也就是说,如果 undefined 和 null 不在首参数,就不会报错。let obj = {a: 1};
Object.assign(obj, undefined) === obj; // true
Object.assign(obj, null) === obj; // true
注:
(1)浅拷贝
Object.assgin 方法实行的是浅拷贝,而不是深拷贝。也就是说,如果源对象某个属性的值是对象,那么目标对拷贝得到的是这个对象的引用。(对于这个深表怀疑,我觉得 Object.assign 并不是真正意义上的浅拷贝,其他资料说的是,对其顶层属性赋值。)
(2)同名属性的替换
对于这种嵌套的对象,一旦遇到同名属性,Object.assign 的处理方法是替换,而不是添加。
(3)数组的处理
Object.assign 可以用来处理数组,但是会把数组视为对象。
Object.assign([1, 2, 3], [4, 5]) // [4, 5, 3]
// 上面代码中,Object.assign 把数组视为属性名为 0、1、2 的对象,因此源数组的 0 号属性 4 覆盖了目标数组的 0 号属性 1。
(4)取值函数的处理
Object.assign 只能进行值得赋值,如果要复制的值是一个取值函数,那么将求值后再复制。
const source = {get foo() {return 1}
};
const target = {};
Object.assign(target, source); // {foo: 1}
常见用途
Object.assign 方法有很多用处。
(1) 为对象添加属性
class Point {constructor(x, y) {Object.assign(this, {x, y});
}
}
(2)为对象添加方法
(3) 克隆对象
function clone(origin) {return Object.assign({}, origin);
}
emmm,这不是深拷贝,只是对顶层属性做了赋值。。。
(4)合并多个对象
(5) 为属性指定默认值
4.__proto__属性,Object.setPrototypeOf(),Object.getPrototypeOf()
JavaScript 语言的对象继承是通过原型链实现的。
__proto__属性
用来读取或设置当前对象的 prototype 对象。目前,所有浏览器(包括 IE11)都部署了这个属性。
// es5 的写法
const obj = {method: function() {...}
};
obj.__proto__ = someOtherObj;
// es6 的写法
var obj = Object.create(someOtherObj);
obj.method = function() { ...};
实际上,__proto__调用的是 Object.prototype.__proto__.
5.Object.keys(), Object.values(), Object.entries()
ES5 引入了 Object.keys 方法,返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键名。
var obj = {foo: 'bar', baz: 42};
Object.keys(obj);
// ["foo", "baz"]
ES2017 引入了跟 Object.keys 配套的 Object.values 和 Object.entries, 作为遍历一个对象的补充手段,供 for…of 循环使用.
Object.entries()
Object.entries()方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的价值对数组。
const obj = {foo: 'bar', baz: 42};
Object.entries(obj); // [["foo", "bar"], ["baz", 42] ]
除了返回值不一样,该方法的行为与 Object.values 基本一致。
Symbol
1. 概述
ES6 引入了一种新的原始数据类型 Symbol,表示独一无二的值。它是 JS 语言的第七种数据类型,前六种是:undefined、null、布尔值(Boolean)、字符串(String)、数值(Number)、对象(Object)。
Symbol 值通过 Symbol 函数生成,也就是说,对象的属性名现在可以有两种类型,一种是原来就有的字符串,另一种就是新增的 Symbol 类型。凡是属性名属于 Symbol 类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。
let s = Symbol(); // 注意,Symbol 函数前不能使用 new 命令,否则会报错。这是因为生成的 Symbol 是一个原始类型的值,不是对象。typeof s; // "symbol"
需要注意的是,Symbol 函数的参数只是表示对当前 Symbol 值的描述,因此相同参数的 Symbol 函数的返回值是不相等的。
// 没有参数的情况
let s1 = Symbol();
let s2 = Symbol();
s1 === s2; // false
// 有参数的情况
let s1 = Symbol('foo');
let s2 = Symbol('foo');
s1 === s2; // false
// Symbol 值不能与其他类型的值进行运算,会报错
let sym = Symbol('My symbol');
'your symbol is' + sym; // TypeError: can't convert symbol to string
// 但是 Symbol 值可以显示转为字符串。let sym = Symbol('My symbol');
String(sym); // 'Symbol(My symbol)'
sym.toString(); // 'Symbol(My symbol)'
……
下面的内容就挑着看了。。。。。
// 去除数组重复成员的方法:[...new Set(array)];
NaN == NaN; // false
let set = new Set();
let a = NaN;
let b = NaN;
set.add(a);
set.add(b);
set; // Set {NaN} 说明在 Set 内部,两个 NaN 是相等的。set = new Set();
set.add({});
set.size; // 1
set.add({});
set.size; // 2 说明两个空对象在 Set 里面是不相等的。
编程风格
1、块级作用域
1)let 取代 var
ES6 提出了两个新的声明变量的命令:let 和 const,其中,let 完全可以取代 var,因为两者的语义相同,而且 let 没有副作用。
2)全局常量和线程安全
在 let 和 const 之间,建议优先使用 const,尤其是在全局环境,不应该设置变量,只应设置常量。
const 比较符合函数式编程思想,运算不改变值,只是新建值,而且这样也有利于将来的分布式运算;并且 JavaSCript 编译器回对 const 进行优化,多使用 const,有利于提高程序的运行效率,也就是说 let 和 const 的本质区别,其实是编译器内部的处理不同。
// bad
var a = 1, b = 2, c = 3;
// good
const a = 1;
const b = 2;
const c = 3;
// best
const [a, b, c] = [1, 2, 3];
2、字符串
静态字符串一律使用单引号或反引号,不使用双引号。动态字符串使用反引号。
// bad
const a = "foobar";
const b = 'foo' + a + 'bar';
// acceptable
const c = `foobar`;
// good
const a = 'foobar';
const b = `foo${a}bar`;
3、解构赋值
使用数组成员对变量赋值时,优先使用解构赋值。
const arr = [1, 2, 3, 4];
// bad
const first = arr[0];
const second = arr[1];
// good
const [first, second] = arr;
函数的参数如果是对象的成员,优先使用解构赋值。
// bad
function getFullName(user) {
const firstName = user.firstName;
const lastName = user.lastName;
}
// good
function getFullName(obj) {const { firstName, lastName} = obj;
}
// best
function getFullName({firstName, lastName}) {}
4、对象
单行定义的对象,最后一个成员不以逗号结尾。多行定义的对象,最后一个成员以逗号结尾。
// bad
const a = {k1: v1, k2: v2,};
const b = {
k1: v1,
k2: v2
};
// good
const a = {k1: v1, k2: v2};
const b = {
k1: v1,
k2: v2
};
对象尽量静态化,一旦定义,就不得随意添加新的属性。如果添加属性可不避免,要使用 Object.assign 方法。
// bad
const a = {};
a.x = 3;
// if reshape unavoidable
const a = {};
Object.assign(a, { x: 3});
// good
const a = {x: null};
a.x = 3;
如果对象的属性名是动态的,可以在创造对象的时候,使用属性表达式定义。
// bad
const obj = {
id: 5,
name: 'San Francisco',
};
obj[getKey('enabled')] = true;
// good
const obj = {
id: 5,
name: 'San Francisco',
[getKey('enabled')]: true
};
// 对象的属性和方法,尽量采用简洁表达式,这样易于描述和书写
var ref = 'some value';
// bad
const atom = {
ref: ref,
value: 1,
addValue: function (value) {return atom.value + value;},
};
// good
const atom = {
ref,
value: 1,
addValue(value) {return atom.value + value;},
};
5、数组
使用扩展运算符(…)拷贝数组。
// bad
const len = items.length;
const itemsCopy = [];
let i ;
for(i = 0; i < len; i++) {itemsCopy[i] = items[i];
}
// good
const itemsCopy = [...items];
使用 Array.from 方法,将类似数组的对象转为数组。
const foo = document.querySelectorAll('.foo');
const nodes = Array.form(foo);
6、函数
立即执行函数可以写成箭头函数的形式。
(() => {console.log('Welcome to the Internet.');
})();
那些使用匿名函数当作参数的场合,尽量用箭头函数代替,因为这样更简洁,而且绑定了 this。
// bad
[1, 2, 3].map(function (x) {return x * x;});
// good
[1, 2, 3].map((x) => {return x * x;});
// bset
[1, 2, 3].map(x => x * x);
使用默认值语法设置函数参数的默认值。
// bad
function handleThings(opts) {opts = opts || {};
}
// good
function handleThings(opts = {}) {}
9、模块
Module 语法是 JavaScript 模块的标准写法。使用 import 取代 require。
// bad
const moduleA = require('moduleA');
const func1 = moduleA.func1;
const func2 = moduleA.func2;
// good
import {func1, func2} from 'moduleA';
使用 export 取代 module.exports。
// commonJS 的写法
var React = require('react')'
var Breadcrumbs = React.createClass=({render() {return <nav />;}
});
module.exports = Breadcrumbs;
// ES6 的写法
import React from 'react';
class Breadcrumbs extends React.Component {render() {return <nav />;}
};
export default Breadcrumbs;
// 如果模块只有一个输出值,就是用 export default,如果模块有多个输出值,就不适用 export default,export default 与普通的 export 不要同时是用。
不要在模块输入中使用通配符。因为这样可以确保你的模块之中,有一个默认输出(export default)。
// bad
import * ad myObject from './importModule';
// good
import myObject from './importModule';
如果模块默认输出一个函数,函数名的首字母应该小写;如果模块默认输出一个对象,对象名的首字母应该大写。
function makeStyleGuide() {}
export default makeStyleGuide;
const StyleGuide = {es6: {}
}
export default StyleGuide;