Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

谈谈ES6语法(汇总上篇) #36

Open
reng99 opened this issue Jul 12, 2019 · 0 comments
Open

谈谈ES6语法(汇总上篇) #36

reng99 opened this issue Jul 12, 2019 · 0 comments
Labels
blog a single blog javascript javascript tag

Comments

@reng99
Copy link
Owner

reng99 commented Jul 12, 2019

ES6可以说是一个泛指,指5.1版本以后的JavaScript的下一代标准,涵盖了ES2015,ES2016,ES2017等;亦指下一代JavaScript语言

背景

嗯~ES6的语法有什么好谈的,无聊了吧?

确实,语法糖的东西真的是学起来如嚼蜡 -- 淡无味;但是要用别人的东西来开发的,你学还是学呢?

所以,还是简单谈下吧...

本次的ES6语法的汇总总共分为上、中、下三篇,本篇文章为上篇。

var、let和const

var是之前就有的了,在这里提出来主要是为了比较其和let与const

区别

1. 块级作用域

for(var i = 0; i < 3; i++) {
	setTimeout(() => {
		console.log(i); // 输出3个3
	}, 0)
}

解析:变量i是var声明的,在全局范围内是都有效,全局只有一个变量i。每次循环,变量的值会发生改变。循环内的i是指向全局的i。

for(let i = 0; i < 3; i++) {
	setTimeout(() => {
		console.log(i); // 输出0, 1, 2
	}, 0)
}

解析:变量i是let声明的,当前的i只在本轮循环有效,所以每次循环的i其实都是一个新变量。JavaScript引擎内部会记住上一轮的值,初始化本轮的变量i时,就在上一轮循环的基础上进行计算。

2. 不存在变量提升

console.log(a); // undefined
var a = 100;

var命令会发生变量提升现象,即变量可以在声明之前使用,值为undefined;而let纠正了这种行为,不能产生变量提升。

console.log(a); // 报错
let a = 100;

3. 暂时性死区

只要块级作用域内,存在let命令,它所声明的变量就绑定(binding)在这个区域,不再受外部影响。

如:

var temp = 123;
if(true) {
	temp = 'abc'; // 引入错误
	let temp; 
}

在上面中,if后面的大括号内容就形成了一个区域。而temp此时是找不到外层的,因为内部有个temp且你在内部let temp声明前赋值了。

在看一个隐晦的例子:

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

在上面的例子中bar里面进行赋值操作的时候,就产生了一个封闭的区域了,可以认为x 和 y通过let声明,可是上面的问题是,x = y的引用在y = 2的声明之前。

可以修正如下:

function bar(y = 2, x = y) {
	return [x, y];
}
bar(); // [2, 2]

4. 不可重复声明

var a = 100;
var a = 1000;
console.log(a); // 1000
let a = 100;
let a = 1000; // 报重复声明错误

5. ES6声明的变量不会挂在顶层对象

嗯~ES6变量的声明是指哪些声明呢?

let, const, import, class声明。

var, function声明是ES6之前的。

所以目前JavaScript有六种声明变量的方式了~

var job = 'teacher';
console.log(window.job); // teacher
let job = 'teacher';
console.log(window.job); // undefined

const命令注意点

  1. let可以先声明稍后赋值;而const声明之后必须立马赋值,否则会报错
let a;
a = 100; // this is ok
const a; // 报没初始化数据的错误
  1. const声明了简单的数据类型就不能更改了;声明了引用类型(数组,对象等),指针指向的地址不能更改,但是内部的数据可以更改的
const str = 'this is a string';
str = 'this is another string'; // 报了个“给不变的变量分配值”的错误
const obj = {
	name: 'jia'
}
obj.name = 'ming'; // this is ok
obj = {}; // 报了个“给不变的变量分配值”的错误

let和const的使用场景

  1. let使用场景:变量,用以代替var

  2. const使用场景:常量、声明匿名函数、箭头函数的时候。

// 常量
const PI = 3.14;

// 匿名函数
const fn1 = function() {
	// do something
}

// 箭头函数
const fn2 = () => {
	// do something
}

变量的解构赋值

解构可以理解就是一个作用:简化你变量赋值的操作。

数组场景

let [name, job] = ['jiaming', 'teacher'];
console.log(name); // jiaming

本质上,这种写法属于模式匹配,只要等号两边的模式相同(重点),左边的变量就会被赋予对应的值。再比如:

let [ , , third] = ["foo", "bar", "baz"];
console.log(third); // "baz"

let [head, body, ...tail] = [1, 2, 3, 4, 5];
console.log(tail); // [3, 4, 5]

也可以使用默认值。但是默认值生效的前提是:ES6内部使用严格相等运算符(===),判断一个位置是否有值。所以,只有当一个数组成员严格等于undefined,默认值才会生效。

let [x, y = 'b'] = ['a']; // x='a', y='b'

let [z = 1] = [undefined];
console.log(z); // 1

let [k = 1] = [null];
console.log(k); // null

对象场景

const state = {
	name: 'jiaming',
	job: 'teacher'
};
let {
	name,
	job
} = state;
// 上面的场景很熟悉吧
console.log(job); // teacher

上面的例子如果写具体的话,是这样的:

const state = {
	name: 'jiaming',
	job: 'teacher'
};
let {
	name: name, // 第一个name是匹配模式,第二个name才是变量,两者同名简化成一个即可
	job: job
} = state;

我们来改写下:

const state = {
	name: 'jiaming',
	job: 'teacher'
};
let {
	name: job,
	job: name
} = state;
console.log(job); // jiaming

对象也可以使用默认值,但是前提是:对象的属性值严格等于undefined

如下:

var {x = 3} = {x: undefined};
console.log(x); // 3

var {y = 3} = {y: null};
console.log(y); // null

字符串场景

字符串之所以能够被解构赋值,是因为此时字符串被转换成了一个类似数组的对象。

const [a, b, ...arr] = 'hello';
console.log(arr); // ["l", "l", "o"]
let {length: len} = 'hello';
console.log(len); // 5

数值和布尔值场景

解构赋值时,如果等号右边是数值和布尔值,则会先转换为对象(分别是基本包装类型Number和基本包装类型Boolean)。不过这种场景用得不多~

let {toString: s} = 123;
console.log(s); // function toString() { [native code] }
console.log(s === Number.prototype.toString); // true
let {toString: s} = true;
console.log(s === Boolean.prototype.toString); // true

解构赋值的规则是,只要等号右边的值不是对象或数组,就先将其转为对象。由于undefined和null无法转为对象,所以对它们进行解构赋值,都会报错

两种使用场景

1. 交换两变量值

let [a, b] = ['reng', 'jia'];
[a, b] = [b, a];
console.log(b); // 'reng'

2. 将字符串转换为数组

let [...arr] = 'reng';
console.log(arr); // ["r", "e", "n", "g"]
console.log(arr.splice(0, 2)); // ["r", "e"] 返回删除的数组(能使用数组的方法了)

字符串扩展

针对字符串扩展这个,个人感觉模版字符串使用的频率比较高。模版字符串解放了拼接字符串带来的繁琐操作的体力劳动。

let name = 'jiaming';
let str = 'Hello! My name is '+ name + '. Nice to meet you!';
let strTemp = `Hello! My name is ${ name }. Nice to meet you!`

对于新增的字符串方法,可以记下下面这几个:

  • includes(): 返回布尔值,表示是否找到了参数字符串
  • startWith(): 返回布尔值,表示参数字符串是否在原字符串的头部
  • endWith(): 返回布尔值,表示参数字符串是否在原字符串的尾部
  • trimStart(): 返回字符串,表示消除参数字符串开头的空格
  • trimEnd(): 返回字符串,表示消除参数字符串结尾的空格

数值扩展

留意下在Number对象上提供的新方法:

  • Number.isFinite(): 返回布尔值,表示参数值是否有限的
  • Number.isNaN(): 返回布尔值,用来检查一个值是否为NaN
Number.isNaN(NaN) // true
Number.isNaN(15) // false
  • Number.isInteger(): 返回布尔值,用来判断一个数值是否为整数

关于Math对象上的方法,遇到要用到时候,查API吧,不然记太多,脑瓜子会疼~

函数扩展

rest参数

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

arguments对象是一个类数组,还得通过Array.prototype.slice.call(arguments)将其转换为真数组;而rest参数直接就可以使用数组的方法了。

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

箭头函数

ES6允许使用“箭头”(=>)定义函数。

const f = v => v; // 注意是有返回值return的啊

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

如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回结果。

const sum = (num1, num2) => num1 + num2;

// 等价于,使用了大括号,那箭头函数里面就要使用return了
const sum = (num1, num2) => { return num1 + num2 }

// 等价于
const sum = function(num1, num2) {
	return num1 + num2
}

使用箭头函数注意点:

  1. 函数体内的this对象,就是定义所在的对象,而不是使用时所在的对象。
  2. 不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
  3. 不可以使用arguments对象,该对象在函数体内不存在的,如果要用,可以用rest参数代替。
  4. 不可以使用yield命令,因此箭头函数不能用作Generator函数。
function foo() {
	setTimeout(() => {
		console.log('id:', this.id); // id: 42
	}, 100);
}

var id = 21;

foo.call({ id: 42 });
// 错误使用箭头函数的例子

const cat = {
	lives: 9,
	jumps: () => { // 箭头函数的错误使用,因为对象不构成单独的作用域
		this.lives--; // this 指向window
	}
}

var button = document.getElementById('press'); // 一个节点对象
button.addEventListener('click', () => { // 箭头函数的this指向window
 	this.classList.toggle('on');
});

// 箭头函数改成`function`匿名函数指向就正确了。

箭头函数适合处理简单的计算,如果有复杂的函数体或读写操纵不建议使用,这样可以提高代码的可读性。

关于尾递归和其优化可以直接看阮先生的文档

找下茬

假设有这么一个需求,需要对二维数组的元素进行反转并被1减。我们来看下下面代码,哪个能实现此需求呢?

// 代码一
const A = [[0,1,1],[1,0,1],[0,0,0]];
const flipAndInvertArr = function(A) {
    A.map(item=>{
        item.reverse().map(r=>1-r)
    })
}
// 代码二
const A = [[0,1,1],[1,0,1],[0,0,0]];
const flipAndInvertArr = A=> A.map(res =>res.reverse().map(r => 1 - r));

运行之后,发现代码二是能实现需求的:

let resultArr = flipAndInvertArr(A);
console.log(resultArr); // [[0, 0, 1], [0, 1, 0], [1, 1, 1]]

嗯~上面已经提到过,箭头函数体加上大括号后,是需要自己手动return的~

我们来改写下代码一,以便符合需求:

const A = [[0,1,1],[1,0,1],[0,0,0]];
const flipAndInvertArr = function(A) {
    return (A.map(item=>{
        return item.reverse().map(r=>1-r)
    }))
}
let result = flipAndInvertArr(A);
console.log(result); // [[0, 0, 1], [0, 1, 0], [1, 1, 1]]

惊喜不,意外不~

参考和后话

本次的ES6语法的汇总总共分为上、中、下三篇,本篇文章为上篇。

更多的内容,请戳我的博客进行了解,能留个star就更好了💨

@reng99 reng99 added blog a single blog javascript javascript tag labels Jul 12, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
blog a single blog javascript javascript tag
Projects
None yet
Development

No branches or pull requests

1 participant