JavaScript 新特性应该这样回答

76 阅读8分钟

我正在参与掘金创作者训练营第5期,点击了解活动详情

JavaScript 新特性

1 声明变量

ES6之前,声明变量只能使用var关键字。而现在,我们有几种不同的选择,而且功能都得到了增强

1.1 const关键字

常量时不能覆盖的变量,一旦声明,其值不能再更改。

常量的值不可以重置,倘若试图覆盖常量的值,控制台会报错

image.png

1.2 let关键字

现在,JavaScript变量具有词法作用域。

使用let关键字,可以把变量的作用域限定在任何代码块中。使用let可以保住全局变量的值

1.3 模板字符串

模板字符串为字符串拼接提供了一种新的方式。还可以在字符串中插入变量。

听别人说模板字符串、模板字面量或字符串模板,其实指的都是这个。

以前,字符串拼接是使用加号把变量的值和字符串组合在一起。

const name = 'xiaoming';

console('我的名字是' + name);

有了模板,我们在创建的字符串中使用 ${} 插入变量的值

const name = 'xiaoming';

console(`我的名字是 ${name}`);

现在一个字符串可以横跨多行,而不会导致代码错误。

以前,在JavaScript代码中直接使用HTML字符串不是一件易事,要把所有的内容卸载一行内。 现在,空白也作为文本的一部分,我们便可以直接插入格式化的HTML,这样易于阅读和理解。

2 创建函数

只要想让JavaScript重复执行一项任务,就可以使用函数。

2.1 函数声明

函数声明或函数定义以function关键字开头,后跟函数的名称,函数所含的JavaScript语句在一对花括号之间定义。

函数在声明之后便可以调用、执行。

// 声明
function fun() {}

// 调用
fun()

2.2 函数表达式

另一种选择是使用函数表达式。这种范式通过变量创建函数。

// 声明
const fun = function() {}

// 调用
fun()

在函数声明和函数表达式之间抉择要明确一点:**函数声明的作用域会被提升,而函数表达式不会。**换句话说,在编写函数声明之前可以调用函数,而创建函数表达式之前不能调用函数,否则会报错。

传递参数

如果想为函数提供动态变量,可以把具名参数传给函数,放在圆括号之间即可。

// 声明
const fun = function(num) {}

// 调用
fun(1)
函数返回值

我们更多时候,希望函数返回一个值,下面为这个函数添加一个return语句。ruturn语句的作用是指定函数的返回值

// 声明
const fun = function(num) {
	return num + 1
}

// 调用
fun(1)

2.3 默认参数

默认参数是ES6规范引入的特性,如果没有为参数提供值,将使用默认值。

如果没有为函数提供参数,它仍然可以正常运行,只不过使用的是默认值。默认参数可以是任何类型,不限于字符串。

// 声明
const fun = function(num = 10) {
	return num + 1
}

// 调用
fun()

2.4 箭头函数

箭头函数时ES6新增的一个特性,非常有用。有了箭头函数,创建函数不再需要使用function关键字了。另外,也经常不使用return关键字。

如可以简化为:

const fun = num => num + 1;

现在,整个函数在一行内就声明完毕了,去掉了function关键字,也去掉了return关键字,箭头指明的就是要返回的值。这种写法的另一个好处是,如果函数只接收一个参数,可以省略参数两侧的圆括号

不过参数超过一个时要放入圆括号内:

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

这个函数可以写在一行内,毕竟只需要返回一个语句。如果多行,要使用花括号。

 const fun = (num1, num2) => {
 	...
 	return num1 + num2;
 };

返回对象

如果想返回一个对象?

这样做是不对的!!!!

const per = (firstName, lastName) => {
	first: firstName,
	last: lastName,
}

运行这段代码,你会看到一个错误:

image.png

修正的的方法是,把要返回的对象放在圆括号中。

const per = (firstName, lastName) => ({
	first: firstName,
	last: lastName,
})

这里缺少的圆括号是JavaScript和React应用中无数bug的根源,千万别忘了!!!

箭头函数和作用域

常规的函数不限定this的作用域。以下述代码为例,在setTimeout回调中,this变成了别的东西,不再是tahoe对象。

const tahoe = {
	mountains: ['Freel', 'Tallac', 'Rubicon', 'Silver'],
	print: function(delay = 1000) {
		setTimeout(function() {
			console.log(this.mountains.join(','));
		}, delay);
	}
}


tahoe.print();// Uncaught TypeError: Cannot read properties of undefined (reading 'join')

出现这个错误的原因在于试图在this上调用.join方法。在控制台输出this,可以看到它引用的是window对象。

console.log(this); // window

image.png

为了解决这个问题,我们可以使用箭头函数语法保全this的作用域。

const tahoe = {
	mountains: ['Freel', 'Tallac', 'Rubicon', 'Silver'],
	print: function(delay = 1000) {
		setTimeout(() =>{
			console.log(this.mountains.join(','));
		}, delay);
	}
}


tahoe.print();// Freel,Tallac,Rubicon,Silver

现在正常了,我们可以使用逗号把几个英文连在一起啦。

务必时刻考虑作用域。

箭头函数不限定this的作用域。

const tahoe = {
	mountains: ['Freel', 'Tallac', 'Rubicon', 'Silver'],
	print: (delay = 1000) => {
		setTimeout(() =>{
			console.log(this.mountains.join(','));
		}, delay);
	}
}


tahoe.print();// Uncaught TypeError: Cannot read properties of undefined (reading 'join')

把print函数改成箭头函数后,this引用的就是窗口了。

3 编译 JavaScript

一项JavaScript新特性提出并获得一定的支持后,社区通常想在所有浏览器都支持之前就开始使用。为了确保你写出的代码能正常在浏览器中运行,要把代码换成兼容性更好的版本。这个过程叫做编译(compile)。最常用于编译JavaScript代码的工具之一是Babel(www.babeljs.io)。

过去,若想使用最新的JavaScript特性,只有等待,几周,几个月,甚至几年,直到浏览器支持。现在,借助Babel可以立即就使用最新的JavaScript特性。加入编译步骤之后,JavaScript与其他语言更像了。然而,这与常规的编译还不一样,代码不被编译成二进制文件,而是转换成更多浏览器可以解析的语法。另外,JavaScript现在有源码了,这意味着,项目中有些文件不会在浏览器中运行。

以一个接受默认参数的箭头函数为例:

const add = (x = 5, y = 10) => console.log(x + y);

使用Babel编译这段代码,生成的代码如下所示:

"use strict";
var add = function add() {
	var x =
		argument.length <= 0 || argument[0] === undefined ? 5 :argument[0];
	var y =
		argument.length <= 0 || argument[1] === undefined ? 10 :argument[1];
     return console.log(x + y);
}

Babel添加了“use strict”声明,在严格模式下运行代码。变量x和y通过arguments数组(你可能熟悉这个技术)模拟成默认参数。编译得到的JavaScript支持范围更广。

如果想更进一步学习Babel,请访问文档网站中的Babel REPL(babeljs.io/repl)。在左侧输入…

编译JavaScript代码的过程通常由webpack或parcel等构建工具自动操作。

4 对象和数组

自ES6起,JavaScript句法新增了一些在对象和数组中限定变量作用域的创造性方式。这些创造性技术在react社区中广泛使用。

4.1 析构对象

析构赋值语句把对象中字段的作用域限定在本地,以及声明将用到哪些值。以下面sandwich对象为例。这个对象有四个键,不过我们只想使用其中两个值。为此,可以把bread和meat的作用域限限定在本地。

const sandwich = {
	bread: "dutch crunch",
	meat: "tuna",
	chese: "swiss",
	toppings: ["lettuce", "tomato", "mustard"]
};
const { bread, meat } = sandwich;
console.log(bread, meat); // dutch crunch tuna

这段代码从对象中取出bread和meat,创建两个局部变量。另外,由于这两个析构的变量是使用let声明的,因此bread和meat的变化对sandwich对象没有影响。

const sandwich = {
	bread: "dutch crunch",
	meat: "tuna",
	chese: "swiss",
	toppings: ["lettuce", "tomato", "mustard"]
};
const { bread, meat } = sandwich;
bread = "garlic";
meat = "turkey";

console.log(bread); // garlic
console.log(meat); // turkey

console.log(sandwich.bread, sandwich.meat); // dutch crunch tuna

对象还可以析构给函数的参数。

我们可以在函数中析构出想要的使用的值

const obj = {
	firstName: 'aa',
	lastName: 'bb'
};
const lordify = ({ firstName }) => {
	console.log(`${firstName} 是我的姓!`);
};

lordify(obj); // aa是我的姓!

我们还可以这样!

const obj = {
	firstName: 'aa',
	lastName: 'bb'
	like: {
		one: '篮球'two: '足球',
	};
};
const lordify = ({ like: {one} }) => {
	console.log(`${one} 是我的爱好!`);
};

lordify(obj); // 篮球是我的爱好!

使用冒号和嵌套的花括号可以从obj对象中析构出like。

4.2 析构数组

值也可以从数组中析构出来。假设我们想把数组中的第一值赋给一个变量。

const [first] = ['1', '2', '3'];

console.log(first); // 1

此外,还可以使用逗号跳过不需要的值,这叫列表匹配(list matching)。在需要跳过的元素位置上使用逗号执行的就是列表匹配。还以前面的数组为例。在前两个值的位置上使用逗号就可以获取第三个值。

const [, , third] = ['1', '2', '3'];

console.log(third); // 3

4.3 对象字面量增强

对象字面量增强与析构相反,它指把对象重新组合成一体。使用对象字面量增强可以把全局作用域中的变量添加到一个对象中。

const name = 'xiaoming';
const like = '篮球'const obj = {name, like};

console.log(obj); // {name: 'xiaoming', like: '篮球'}

定义对象方法时无须使用function关键字。

// 旧方法
var skier = {
	name: name,
	sound: sound,
	fun: function() {
		console.log('123456');
	}
};

// 新语法
// 旧方法
const skier = {
	name,
	sound,
	fun() {
		console.log('123456');
	}
};

使用对象字面量增强可以把全局变量放到对象中,还可以省略function关键字,减少输入量。

4.4 展开运算符

展开运算符是三个点号(...),可用于执行几项不同的任务。首先,可以使用展开运算符合并数组的内容。

假如有两个数组,我们可以把它们合并成一个数组,创建第三个数组。

const arr1 = ['xiaoming', 'xiaowang', 'xiaoli'];
const arr2 = ['小明', '小王', '小李'];

const arr3 = [...arr1, ...arr2];

console.log(arr3.join(',')); // xiaoming,xiaowang,xiaoli,小明,小王,小李

下面来看展开运算符可以解决一个问题。

以上面 arr1 数组为例,这次我们不想获取第一个元素,而是获得最后一个元素。为此我们可以使用 Array.reverse 方法反转数组,再析构数组

const arr1 =  ['xiaoming', 'xiaowang', 'xiaoli'];
const [last] = arr1.reverse();

console.log(last); // xiaoli
// 原数组被改变了
console.log(arr1); // ['xiaoli', 'xiaowang', 'xiaoming']

在有展开运算符的情况中,我们无须改变原数组,而是创建一个副本,再做反转。

const arr1 =  ['xiaoming', 'xiaowang', 'xiaoli'];
const [last] = [...arr1].reverse();

console.log(last); // xiaoli
// 原数组没有被改变了
console.log(arr1); // ['xiaoming', 'xiaowang', 'xiaoli']

由于我们使用展开运算符创建了一个副本,arr1数组完好无损。仍可以原来的形式继续使用。

展开运算符也可以用于获取数组中剩余的元素:

const arr2 = ['小明', '小王', '小李', '小傻瓜'];
const [first, ...others] = arr2;

console.log(others.join(',')); // 小王,小李,小傻瓜

我们还可以使用三个点号句法把函数收集到一个数组中。在函数中,这叫剩余参数(rest parameters)。这里,我们可以使用展开运算符定义一个可以接受n个参数的函数,然后使用它们:

function funArr(...args) {
	let [first, ...arr] = args;
	console.log(first);
	console.log(arr);
}
funArr('1', '2', '3', '4');
// 1
// ['2', '3', '4']

展开运算符还可以用于处理对象。

使用展开运算符处理对象的方式与处理数组类似。

const obj = {
	name: 'xiaoming',
	like: '篮球',
};

const work = '程序员';

const obj2 = {
	...obj,
	work
};

console.log(obj2); // {name: 'xiaoming', like: '篮球', work: '程序员'}