块级作用域
先说说没有块级作用域,可能产生的问题。
1、内层变量覆盖外层变量
var temp=1;
function f(){
console.log(temp);
if(false){
var temp='字符串变量';
}
}
f()
运行结果:
undefined
因为变量提升,导致 if 语句体中的变量 temp 覆盖了外层变量 temp。
2、用于循环的计数变量,其实是全局变量
var s = 'es6';
for (var i = 0; i < s.length; i++) {
s[i];
}
console.log('i=' + i);
运行结果:
i=3
块级作用域
- 使用 {...} 结构,就可以标注出一个块级作用域。
- 块级作用域里面的用let 命令声明的变量和 const 命令声明的常量的作用域只在这个块里面,不会出现内层变量覆盖外层变量和变量泄露为全局变量的现象
- 块级作用域对var命令无效
//块级作用域对var命令无效,a变量泄露到外层作用域了
if (true) {
var a = 'apple';
}
console.log(a); //apple
//let由于块级作用域的作用,不会泄露为全局变量
if (true) {
let b = 'banana';
}
console.log(b); //Error: b is not defined
//const由于块级作用域的作用,也不会泄露为全局变量
if (true) {
const o = 'orange';
}
console.log(o); //o is not defined
//每一对“{}”都是一个块级作用域,而且互不干扰。
{
let a = 'apple';
{
let a = 'aaa';
{
let a = 'AAA';
}
}
}
函数默认参数
(一)概念
在ES6中,可以为函数的参数指定默认值。函数默认参数允许在没有值或undefined被传入时使用默认形参。
function log(x, y = 'World') {
console.log(x, y);
}
log('Hello') // Hello World
log('Hello', 'China') // Hello China
log('Hello', '') // Hello
(二)默认参数使用注意点
- 参数变量是默认声明的,所以不能用let或const再次声明。
function foo(x = 5) {
let x = 1; // error
const x = 2; // error
}
- 使用参数默认值时,函数不能有同名参数。
// 不报错
function foo(x, x, y) {
// ...
}
// 报错
function foo(x, x, y = 1) {
// ...
}
// SyntaxError: Duplicate parameter name not allowed in this context
- 显式传入undefined或不传值时使用函数默认参数值;传入''或null时使用传入的参数值。
function test(num = 1) {
console.log(typeof num);
}
test(); // 'number' (num is set to 1)
test(undefined); // 'number' (num is set to 1 too)
// test with other falsy values:
test(''); // 'string' (num is set to '')
test(null); // 'object' (num is set to null)
- 参数默认值不是传值的,而是在函数被调用时,参数默认值才会被解析。
function append(value, array = []) {
array.push(value);
return array;
}
append(1); //[1]
append(2); //[2], not [1, 2]
- 位置在前的默认参数可用于后面的默认参数。
function greet(name, greeting, message = greeting + ' ' + name) {
return [name, greeting, message];
}
greet('David', 'Hi'); // ["David", "Hi", "Hi David"]
greet('David', 'Hi', 'Happy Birthday!'); // ["David", "Hi", "Happy Birthday!"]
- 通常情况下,定义了默认值的参数,应该是函数的尾参数。因为这样比较容易看出来,到底省略了哪些参数。如果非尾部的参数设置默认值,实际上这个参数是没法省略的。
// 例一
function f(x = 1, y) {
return [x, y];
}
f() // [1, undefined]
f(2) // [2, undefined])
f(, 1) // 报错
f(undefined, 1) // [1, 1]
// 例二
function f(x, y = 5, z) {
return [x, y, z];
}
f() // [undefined, 5, undefined]
f(1) // [1, 5, undefined]
f(1, ,2) // 报错
f(1, undefined, 2) // [1, 5, 2]
- 指定了默认值以后,函数的length属性,将返回没有指定默认值的参数个数。如果设置了默认值的参数不是尾参数,那么length属性也不再计入后面的参数了。后文的 rest 参数也不会计入length属性。
(function (a) {}).length // 1
(function (a = 5) {}).length // 0
(function (a, b, c = 5) {}).length // 2
(function(...args) {}).length // 0
(function (a = 0, b, c) {}).length // 0
(function (a, b = 1, c) {}).length // 1
转自虹猫1992
剩余(rest)参数
(一)概念
ES6 引入 rest 参数(形式为...变量名),用于获取函数的多余参数,这样就不需要使用arguments对象了。rest参数搭配的变量是一个数组,该变量将多余的参数放入数组中。
function add(...values) {
let sum = 0;
for (var val of values) {
sum += val;
}
return sum;
}
add(2, 5, 3) // 10
(二)rest 参数使用注意点
- rest 参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。
// 报错
function f(a, ...b, c) {
// ...
}
- 函数的length属性,不包括 rest 参数。
(function(a) {}).length // 1
(function(...a) {}).length // 0
(function(a, ...b) {}).length // 1
- rest参数可以被解构,这意味着他们的数据可以被解包到不同的变量中。
function f(...[a, b, c]) {
return a + b + c;
}
f(1) // NaN (b and c are undefined)
f(1, 2, 3) // 6
f(1, 2, 3, 4) // 6 (the fourth parameter is not destructured)
(三)rest参数和 arguments对象的区别
- rest参数只包含那些没有对应形参的实参,而arguments对象包含了传给函数的所有实参。
- arguments对象不是一个真正的数组,而rest参数是真正的Array实例,也就是说你能够在它上面直接使用所有的数组方法,比如 sort,map,forEach或pop。
- arguments对象还有一些附加的属性 (如callee属性)。
展开运算符...xxx
(一)概念
展开运算符,将一个数组转为用逗号分隔的参数序列
(二)使用
- 合并数组
let a = [1,2,3];
let b = [4,5,6];
let c = [...a,...b]; // [1,2,3,4,5,6]
- 替代apply
function f(a,b,c){
console.log(a,b,c)
}
let args = [1,2,3];
// 以下三种方法结果相同
f.apply(null,args)
f(...args)
f(1,2,3)
function f2(...args){
console.log(args)
}
f2(1,2,3) // [1,2,3]
function f3(){
console.log(Array.from(arguments))
}
f3(1,2,3) // [1,2,3]
- Array.from() 可以通过以下方式来创建数组对象:
伪数组对象(拥有一个 length 属性和若干索引属性的任意对象) 可迭代对象(可以获取对象中的元素,如 Map和 Set 等)
let a = [1,2,3];
let b = [4,5,6];
Array.prototype.push.apply(a,b);
// 或
a.push(...b)
// 两种方法取其一
- 解构赋值
let a = [1,2,3,4,5,6]
let [c,...d] = a
console.log(c); // 1
console.log(d); // [2,3,4,5,6]
//展开运算符必须放在最后一位
- 字符串转为数组,正确识别 32 位的 Unicode 字符
[...'siva'] // ['s','i','v','a']
[...'x\uD83D\uDE80y'].length // 3
- 具有 Iterator 接口的对象,转换成数组
var nodelist = document.querySelectorAll('div');
console.log([...nodelist]) // 转化成数组
var map = new Map([[1,11],[2,22],[3,33]]);
console.log([...map.keys()]); // [1,2,3]
- 浅拷贝
//数组
var a = [1,2,4]
var b = [...a]
a.push(6)
console.log(b) // [1,2,4]
//对象
var a = {a:1}
var b = {...a}
a.a = 5
console.log(b.a) // 1
作者:一二三kkxx 链接:www.jianshu.com/p/3935a8034… 来源:简书 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
模板字符串
(一)概念
- 模板字面量 是允许嵌入表达式的字符串字面量。你可以使用多行字符串和字符串插值功能。
- 模板字符串使用反引号 (
) 来代替普通字符串中的用双引号和单引号。模板字符串可以包含特定语法(${expression})的占位符。 - 所有模板字符串的空格和换行,都是被保留的,如果你不想要前后换行,可以使用trim方法消除它。
- 在
${expression}你可以写任意JavaScript表达式,包括调用函数
var x = 1;
var y = 2;
`${x} + ${y} = ${x + y}`
// "1 + 2 = 3"
`${x} + ${y * 2} = ${x + y * 2}`
// "1 + 4 = 5"
var obj = {x: 1, y: 2};
`${obj.x + obj.y}`
// "3"
function fn() {
return "Hello World";
}
`foo ${fn()} bar`
// foo Hello World bar
(二)使用
- 多行字符串
所有模板字符串的空格和换行,都是被保留的。要获得多行字符串直接换行就行。
console.log(`string text line 1
string text line 2`);
// "string text line 1
// string text line 2"
- 在反引号里使用
${expression}插入表达式
var a = 5;
var b = 10;
console.log(`Fifteen is ${a + b} and
not ${2 * a + b}.`);
// "Fifteen is 15 and
// not 20."
- 嵌套模板
在某些时候,嵌套模板是具有可配置字符串的最简单也是更可读的方法。 在模板中,只需在模板内的占位符 ${expression} 内使用它们,就可以轻松地使用内部反引号。 例如,如果条件 a 是真的,那么返回这个模板化的文字。
const classes = `header ${ isLargeScreen() ? '' :
(item.isCollapsed ? 'icon-expander' : 'icon-collapser') }`;
- 带标签的模板字符串
更高级的形式的模板字符串是带标签的模板字符串。标签使您可以用函数解析模板字符串。标签函数的第一个参数包含一个字符串值的数组。其余的参数与表达式相关。最后,你的函数可以返回处理好的的字符串(或者它可以返回完全不同的东西 , 如下个例子所述)。用于该标签的函数的名称可以被命名为任何名字。
var person = 'Mike';
var age = 28;
function myTag(strings, personExp, ageExp) {
var str0 = strings[0]; // "that "
var str1 = strings[1]; // " is a "
// There is technically a string after
// the final expression (in our example),
// but it is empty (""), so disregard.
// var str2 = strings[2];
var ageStr;
if (ageExp > 99){
ageStr = 'centenarian';
} else {
ageStr = 'youngster';
}
return str0 + personExp + str1 + ageStr;
}
var output = myTag`that ${ person } is a ${ age }`;
console.log(output);
// that Mike is a youngster
对象属性增强
- 属性初始化语法简写
给一个属性赋一个变量值,如果变量名和属性名相同,可以省略变量名和冒号,直接写属性名,js引擎在执行代码的时候,自动查找 和属性命名相同的变量并赋值。
let x = 1, y = 2;
let object = {
  x, // 属性名是x,变量名也是x, 两者一致,可以简写
  y
};
console.log(object.x); //output "1"
- 更为简洁的方法属性定义
ES5的时候,把一个函数赋值给属性的时候,函数必须是一个完整的函数定义
let object = {
myFunction: function(){
console.log("Hello World")
}
}
但是在ES6中,可以把:function 这一部分去掉了,写法如下
let object = {
myFunction(){
console.log("Hello World")
}
}
语法确实简洁多了,不过要注意一个特殊情况,只有给属性赋值的是匿名函数的时候,才可以使用简洁语法,如果赋值的是一个有名字的函数,那么就不能使用匿名函数了。如下情况就不能
let object = {
myFunction: function hello(){
console.log("Hello World")
}
}
函数hello 赋值给属性myFunction, 你可能会问,为什么要给函数取一个hello 名字,最常见的一种情况是递归,自己调用自己,如果没有名字,怎么调用?还有就是程序debugger 的时候,有函数名字可以直接定位, you don't know js 的作者就强烈建议书写有名字的函数。
- 计算属性名
ES5 的时候,对象字面量中的属性都是事先定义好的, 不能使用变量,从而在程序运行的时候动态生成属性
但在ES6中,这种情况改变了,对象字面量中可以存在动态生成的属性,不过语法就要稍微变一下了,需要把动态属性用[] 包括起来,这样在程序运行的时候可以对[] 中的内容进行计算
let object = {
  ["first" + "Name"]: "Eden",
};
//extract
console.log(object["first" + "Name"]); //Output "Eden"
- 对重复属性名的处理
在ES5 的时候,如果给一个对象赋值为相同的属性,它就会报错。但在ES6 下,它不会报错了,它会取最后一个相同属性的值。
let obj = {
name: 'Sam',
name: 'Jason'
};
console.log(obj.name) // 'jason'
解构赋值
通过解构赋值, 可以将属性/值从对象/数组中取出,赋值给其他变量。
var a, b, rest;
[a, b] = [10, 20];
console.log(a); // 10
console.log(b); // 20
[a, b, ...rest] = [10, 20, 30, 40, 50];
console.log(a); // 10
console.log(b); // 20
console.log(rest); // [30, 40, 50]
({ a, b } = { a: 10, b: 20 });
console.log(a); // 10
console.log(b); // 20
// Stage 4(已完成)提案中的特性
({a, b, ...rest} = {a: 10, b: 20, c: 30, d: 40});
console.log(a); // 10
console.log(b); // 20
console.log(rest); // {c: 30, d: 40}