1.let 和 const 命令
let 特性:
- 用来声明变量,只在它所在的代码块中有效,所以在此块级作用域中不可重复声明;
- 不存在变量提升,声明后必须使用;
- 块级作用域中未声明就是用会报错(暂时性死区);
例1:
// var 存在变量提升,undefined是一种数据类型
console.log(foo); // 输出undefined
var foo = 2;
// let 不声明就使用会报错
console.log(bar); // 报错ReferenceError
let bar = 2;
const 特性:
- 具有上面 let 的三个特性;
- 声明后所指向的内存地址不会变动: 声明的简单类型的数据(数值、字符串、布尔值)等同于常量,复合类型的数据(比如对象、数组)只保存指针,原数据的数据结构是不是可变的,不能控制,;
const a = {value:1}
a.value = 2
console.log(a) //{value:2}
const b =[1,2,3]
b.push(4)
console.log(b) //[1,2,3,4]
2.解构赋值
ES6 允许按照一定的模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。
1.数组的解构
let [head, ...tail] = [1, 2, 3, 4];
head // 1
tail // [2, 3, 4]
let [x, y, ...z] = ['a'];
x // "a"
y // undefined 解构不成功,变量的值就等于undefined。
z // []
2.对象的解构
解构对象时变量名与属性同名才能取到正确的值;
let { bar, foo } = { foo: "aaa", bar: "bbb" };
foo // "aaa"
bar // "bbb"
let { baz } = { foo: "aaa", bar: "bbb" };
baz // undefined
当然变量名与属性名不一致时,可以这样写:
let { foo: baz } = { foo: 'aaa', bar: 'bbb' };
baz // "aaa"
let obj = { first: 'hello', last: 'world' };
let { first: f, last: l } = obj;
f // 'hello'
l // 'world'
对象的解构赋值的内部机制是先找到同名属性,然后在赋给对应的变量,所以,真正被赋值的是后者而不是前者。对象可以嵌套解构,可以指定默认值;其他类型也可以解构赋值,这里就不一一说明了;
3.解构赋值的部分使用场景
使用场景有很多,比如交换变量的值;
let x = 1;
let y = 2;
[x, y] = [y, x];
提取 JSON 数据:
let jsonData = {
id: 42,
status: "OK",
data: [867, 5309]
};
let { id, status, data: number } = jsonData;
console.log(id, status, number);
// 42, "OK", [867, 5309]
遍历 Map 结构
const map = new Map();
map.set('first', 'hello');
map.set('second', 'world');
for (let [key, value] of map) {
console.log(key + " is " + value);
}
// first is hello
// second is world
// 获取键名
for (let [key] of map) {
// ...
}
// 获取键值
for (let [,value] of map) {
// ...
}
3.数据类型的扩展
1.字符串的扩展
字符串的扩展有很多,我这里写一下我平时常用的几点。
let s = 'Hello world!';
s.startsWith('Hello') // true ;表示参数字符串是否在原字符串的尾部
s.endsWith('!') // true ;表示参数字符串是否在原字符串的头部
s.includes('o') // true ;是否找到参数字符串
repeat 方法返回一个新字符串,表示将原字符串重复n次。参数如果是小数则取整,不能小于等于-1,-1(不包含)到1(不包含)视同为0,NAN也是一样,数字型的字符串会自动转换,不是则为“”;
'hello'.repeat(2) // "hellohello"
padStart()用于头部补全,padEnd()用于尾部补全。
'x'.padStart(5, 'ab') // 'ababx'
'x'.padStart(4) // ' x',第二个参数不填表示为空格
'x'.padEnd(5, 'ab') // 'xabab'
'x'.padEnd(4, 'ab') // 'xaba'
'12'.padStart(10, 'YYYY-MM-DD') // "YYYY-MM-12"
'09-12'.padStart(10, 'YYYY-MM-DD') // "YYYY-09-12" //用来提示字符串格式
还有一个很重要的扩展。模板字符串的写法:用反引号(`)标识。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量,变量名卸载${}之中,或者也可以调用函数,也可以嵌套模板。
function authorize(user, action) {
if (!user.hasPrivilege(action)) {
throw new Error(
// 传统写法为
// 'User '
// + user.name
// + ' is not authorized to do '
// + action
// + '.'
`User ${user.name} is not authorized to do ${action}.`);
}
}
通过JSX函数还可以将一个DOM字符串转为React对象,具体实现 ;
2.数值的扩展
在Number的对象上提供了Number.isFinite()和Number.isNaN()方法,分别用来检查一个数值是否是有限的,和检查一个值是否为NaN;
还有一个很重要的扩展。模板字符串的写法:用反引号(`)标识。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量,变量名卸载${}之中,或者也可以调用函数,也可以嵌套模板。
``` javascript
Number.isFinite('10'); // false 非数值一律返回false
Number.isNaN(NaN) // true 参数不是数值一律返回NaN,只有NaN才返回true
Number.isNaN(15) // false
Math.trunc方法用于去除一个数的小数部分,返回整数部分。
Math.trunc('123.456') // 123 内部会自动转换为数值
Math.trunc(true) //1
Math.trunc(false) // 0
Math.trunc(null) // 0
Math.trunc(); // NaN
Math.trunc(undefined) // NaN 空值和无法截取整数的返回NaN
Math.sign方法用来判断一个数到底是正数、负数、还是零。对于非数值,会先将其转换为数值。无法转换的返回NaN;
Math.sign(-5) // -1
Math.sign(5) // +1
Math.sign(0) // +0
Math.sign(-0) // -0
Math.sign(NaN) // NaN
3.函数的扩展
对函数的拓展最大的特点就是能为函数的参数指定默认值。
function Point(x = 0, y = 0) {
this.x = x;
this.y = y;
}
const p = new Point();
p // { x: 0, y: 0 }
函数的name属性,返回改函数的函数名。
const bar = function baz() {};
bar.name // "baz"
(new Function).name // "anonymous"
还有一个重大更新:箭头函数。
var f = () => 5;
// 等同于
var f = function () { return 5 };
var sum = (num1, num2) => num1 + num2;
// 等同于
var sum = function(num1, num2) {
return num1 + num2;
};
由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号,否则会报错。有四点需要注意:
- 函数体内的this对象是定义时所在的对象,是固定的,而不是使用时的对象,因为箭头函数没有自己的this对象。
- 不能使用new命令。
- 不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
- 不可以使用yield命令,因此箭头函数不能用作 Generator 函数。
- 箭头函数没有自己的this,所以当然也就不能用call()、apply()、bind()这些方法去改变this的指向。
// ES6
function foo() {
setTimeout(() => {
console.log('id:', this.id);
}, 100);
}
// ES5
function foo() {
var _this = this;
setTimeout(function () {
console.log('id:', _this.id);
}, 100);
}
双冒号运算符是并排的两个冒号(::)。冒号左边是对象,邮编是函数,表示将左边的对象作为this对象,绑定到右边的函数上面。
4.扩展运算符
三个点(...),主要用于函数调用,比如将一个数组,变为参数序列。
function push(array, ...items) {
array.push(...items);
}
function add(x, y) {
return x + y;
}
const numbers = [4, 38];
add(...numbers) // 42
// ES5 的写法
function f(x, y, z) {
// ...
}
var args = [0, 1, 2];
f.apply(null, args);
// ES6的写法
function f(x, y, z) {
// ...
}
let args = [0, 1, 2];
f(...args);
//复制数组
const a1 = [1, 2];
// 写法一
const a2 = [...a1];
// 写法二
const [...a2] = a1;
任何 Iterator 接口的对象(参阅 Iterator 一章),都可以用扩展运算符转为真正的数组。
let nodeList = document.querySelectorAll('div');
let array = [...nodeList];
Array.from()可以将类似数组的对象和可遍历的对象转换为真正的数组;
// NodeList对象
let ps = document.querySelectorAll('p');
Array.from(ps).filter(p => {
return p.textContent.length > 100;
});
// 函数内部的arguments对象
function foo() {
var args = Array.from(arguments);
// ...
}
Array.of方法用于将一组值,转换为数组。
Array.of(3, 11, 8) // [3,11,8]
Array.of(3) // [3]
Array.of(3).length // 1
Object.assign方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。
const target = { a: 1 };
const source1 = { b: 2 };
const source2 = { c: 3 };
Object.assign(target, source1, source2);
target // {a:1, b:2, c:3} assign方法用的浅拷贝
Object.assign方法有很多用处,比如为对象添加属性,为对象添加方法,合并对象,为属性指定默认值等等;
// 为对象添加属性
class Point {
constructor(x, y) {
Object.assign(this, {x, y});//Object.assign方法,将x属性和y属性添加到Point类的对象实例。
}
}
// 将多个对象合并到某个对象。
const merge =
(target, ...sources) => Object.assign(target, ...sources);
扩展运算符的结构赋值不能复制继承自原型对象的属性。
let o1 = { a: 1 };
let o2 = { b: 2 };
o2.__proto__ = o1;
let { ...o3 } = o2;
o3 // { b: 2 }
o3.a // undefined 对象o3复制了o2,但是只赋值了o2自身的属性,没有赋值它的原型对象o1的属性。
如果想完整克隆一个对象,还拷贝对象原型的属性可以用一下方法。
// 写法一 :__proto__属性在非浏览器的环境不一定部署
const clone1 = {
__proto__: Object.getPrototypeOf(obj),
...obj
};
// 写法二
const clone2 = Object.assign(
Object.create(Object.getPrototypeOf(obj)),
obj
);
// 写法三
const clone3 = Object.create(
Object.getPrototypeOf(obj),
Object.getOwnPropertyDescriptors(obj)
)
4.Symbol
Symbol是一种新的原始数据类型,值是通过Symbol()函数生成,可以设置多个,属性名属于Symbol类型的都是独一无二的,即使继承也不会传递,其值也不能用于运算。
let mySymbol = Symbol();
// 第一种写法
let a = {};
a[mySymbol] = 'Hello!';
// 第二种写法
let a = {
[mySymbol]: 'Hello!'
};
// 第三种写法
let a = {};
Object.defineProperty(a, mySymbol, { value: 'Hello!' });
// 以上写法都得到同样结果
Symbol还可以通过Symbol.for()生成,但与Symbol()有很大区别。
Symbol.for()与Symbol()这两种写法,都会生成新的 Symbol。它们的区别是,前者会被登记在全局环境中供搜索,后者不会。Symbol.for()不会每次调用就返回一个新的 Symbol 类型的值,而是会先检查给定的key是否已经存在,如果不存在才会新建一个值。比如,如果你调用Symbol.for("cat")30 次,每次都会返回同一个 Symbol 值,但是调用Symbol("cat")30 次,会返回 30 个不同的 Symbol 值。
let s1 = Symbol.for('foo');
let s2 = Symbol.for('foo');
s1 === s2 // true
如果有需求是跨iframe或者service worker的,Symbol.for设置的值是全局环境的,是登记了的,而Symbol()写法没有登记机制,每次调用都会返回一个不同的值,如果要获取已登记的Symbol类型值得key可以通过Symbol.keyFor()方法。
let s1 = Symbol.for("foo");
Symbol.keyFor(s1) // "foo"
let s2 = Symbol("foo");
Symbol.keyFor(s2) // undefined
5.Set和Map数据结构
Set 同Java中的Set一样的,不能有重复的数据,内置了增-add()、删-delete()、判断-has()、清空-clear()、转换数组-Array.from().;另外还有一些遍历的方法,但是在遍历中不能改变原Set结构。
let set = new Set(['red', 'green', 'blue']);
let arr = [...set];
// ['red', 'green', 'blue']
// 去除数组的重复成员。
let arr = [3, 5, 2, 2, 5, 5];
let unique = [...new Set(arr)];
// [3, 5, 2]
// 改变原Set结构思路
// 方法一
let set = new Set([1, 2, 3]);
set = new Set([...set].map(val => val * 2));
// set的值是2, 4, 6
// 方法二
let set = new Set([1, 2, 3]);
set = new Set(Array.from(set, val => val * 2));
// set的值是2, 4, 6
WeakSet结构与Set类似,只不过成员只能是对象,而且其中的队形都是弱引用,适合临时存放一组对象,不可遍历。
Map用法同Java中也差不多,键值对结构,可以把对象当做键,但是只有对同一个对象的引用,Map结构才会视为同一个键。
const map = new Map();
map.set(['a'], 555);
map.get(['a']) // undefined 表面是同一个键,实际内存地址不一样
const k1 = ['b'];
const k2 = ['b'];
map
.set(k1, 111)
.set(k2, 222);
map.get(k1) // 111
map.get(k2) // 222
6.Proxy
Proxy勇于修改某些操作的默认行为,简单的说就是在访问目标对象之前做拦截接,要对目标访问都必须先通过注册拦截,这样就可以对外界的访问进行过滤和改写了。
需要用到了再具体研究。
7.Reflect
Reflect有多用处,比如让object操作都变成函数行为:
delete obj[name];
//改为
Reflect.deleteProperty(obj, name);
将Object 对象的一些明显属于语言内部的方法放到Reflect对象上;
// 老写法
try {
Object.defineProperty(target, property, attributes);
// success
} catch (e) {
// failure
}
// 新写法
if (Reflect.defineProperty(target, property, attributes)) {
// success
} else {
// failure
}
还有以下Reflect与Proxy结合使用的例子,这里也不一一举例了。
8.Promise
Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。
从语法上说,Promise是一个对象,可以获取一部操作的消息。
Promise对象有两个特点:对象的状态(pengding/fufilled/rejected)不收外界影响;一旦状态改变就不会再变,任何时候都可以得到这个结果。
promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});
9.Generator函数
Generator函数从语法上可以理解为一个状态机,封装了多个内部状态;执行Generator函数会返回一个遍历器对象,可以依次遍历Generator函数内部的每一个状态。
Generator函数两个特征;function关键字和函数名之间有个*号,函数体内部使用yield表达式,定义不同的内部状态。
function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
var hw = helloWorldGenerator();
hw.next()
// { value: 'hello', done: false }
hw.next()
// { value: 'world', done: false }
yield表达式就是暂停标志。yield表达式后面的表达式,只有当调用next方法、内部指针指向该语句时才会执行,当处于for...of循环体内时,循环会自动调用。
当一个对象不具备 Itreator接口是,无法使员工for...of遍历,这是可以用Genertaorg函数为这个对象加上遍历器接口;或者把Generator函数添加到对象的Symbol,iterator属性上。
function* objectEntries() {
let propKeys = Object.keys(this);
for (let propKey of propKeys) {
yield [propKey, this[propKey]];
}
}
let jane = { first: 'Jane', last: 'Doe' };
jane[Symbol.iterator] = objectEntries;
for (let [key, value] of jane) {
console.log(`${key}: ${value}`);
}
// first: Jane
// last: Doe