感谢 阮一峰 老师的技术资料!!
let和const命令
1.let命令
a. 只在代码块中生效
b. 拒绝变量提升
c. 暂时性死区
d. 不允许重复申明
在es6新增了 let 命令,用来声明变量。他的用法类似于 var ,但是所声明的代码只在 let 命令所在的代码块内部有效。
function testlet(){
let a = 10;
var b = 1;
}
console.log(a)// ReferenceError: a is not defined. a 只会在上部分代码块中生效 超出即无效
console.log(b) // 1
let 非常适合与 for 结合使用
for(let i =0;i<5;i++){
//业务逻辑
}
拒绝变量提升
var 命令会发生"变量提升"现象,即变量可以在声明之前使用,脚本并不会报错,只是输出undefind;但是 let 禁止了此操作,强行输出,则报语法错.
// var 情况
console.log(foo); //输出undefinded
var foo = 2;
//let
console.log(foo); // 语法报错 ReferenceError
let foo = 2;
总结 : 对于var , 脚本开始运行时,变量foo已经存在,但没有值,因此会输出undefinded.
对于let ,脚本开始运行时,变量 foo并不存在,此时调用 foo,就会报错
暂时性死区
暂时性死区和拒绝变量提升有着一些关联,同样牵扯到使用 let 申明时,变量不允许在未申明完成之前使用.
如下:
var temp = 5;
if(true){
console.log(temp); //ReferenceError
let temp;
}
虽然在最外侧 temp已经被申明,但是一旦某个块代码中含有 let ,并且 let 也申明了此变量,那么这个块代码就被后者绑定,因此在 'let temp’之前输出 temp,会报引用错误----“即便 temp已经声明为全局变量”!
在 let 面世之前,typeof是100%安全的操作:
//nodecalertemp 是一个未被声明的变量
typeof(nodecalertemp); //undefinded 不报错
然而!
typeof(nodecalertemp); //ReferenceError 报引用错误!
let nodecalertemp; //let申明变量 nodecalrtemp
let 带来的“暂时性死区”也意味着, typeof不再100%安全
不允许重复声明
let不允许在同一个区域内重复声明变量或者形参!
// 报错
function func() {
let a = 10;
var a = 1;
}
// 报错
function func() {
let a = 10;
let a = 1;
}
function func1(arg) {
let arg;
}
func1() // 报错
function func2(arg) {
{
let arg;
}
}
func2() // 不报错
2.块级作用域
❓:为什么需要块级作用域?
在es5 时代,只存在着全局作用域和函数作用域,却没有块级作用域,这带来许多并不合理的场景。
第一种:内层变量可能会覆盖外层变量
var tmp = new Date();
function f() {
console.log(tmp);
if (false) {
var tmp = 'hello world';
}
}
f(); // undefined
第二种:用来计数的循环变量泄露为全局变量
var s = 'hello';
for (var i = 0; i < s.length; i++) {
console.log(s[i]);
}
console.log(i); // 5
当将 var 改换成 let 后,程序就会还原正常状态
3.const命令
const命令声明一个只读常量, const声明的标识符不可以被更改,这意味着, const一旦开始声明,就必须立即初始化,不允许留到以后赋值,否则报错
const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心
const arr = [];//使用const 定义 数组 arr
//虽然const用于定义常量,但针对于一个对象来说
//我们依然可以改变它的属性值 或者 它的索引项
arr[0] = "hello";
console.log(arr); //["hello"]
const obj = {};
obj.a = "hello";
console.log(obj); //{"a":"hello"}
我们可以为 const定义的对象中的属性或索引项执行更改操作,但 不能 直接对该对象更改!
arr = []; //报错 此操作是直接对数组根地址进行操作!
obj = {}; //报错 此操作是直接对对象根地址进行操作!
如果我们真的希望将对象冻结下来,应当使用 Object.freeze 方法。
const constantsize = (obj) =>{
Object.freeze(obj);
Object.keys(obj).forEach( (key, i) => {
if ( typeof obj[key] === 'object' ) {
constantize( obj[key] );
}
});
}
解构赋值
变量的解构赋值
基本用法:es6允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构。
来看一下 es6之前与之后的变量赋值:
之前:
let a = 1;
let b = 2;
let c = 3;
之后:
let [a,b,c] = [1,2,3];
上述代码的本质是“模式匹配”,只需要等号两边的模式相同,左边的变量就会被赋予相应的值。
下面是一些使用嵌套数组进行解构的列子:
let [foo, [[bar], baz]] = [1, [[2], 3]];
foo // 1
bar // 2
baz // 3
let [ , , third] = ["foo", "bar", "baz"];
third // "baz"
let [x, , y] = [1, 2, 3];
x // 1
y // 3
let [head, ...tail] = [1, 2, 3, 4];
head // 1
tail // [2, 3, 4]
let [x, y, ...z] = ['a'];
x // "a"
y // undefined
z // []
结构不成功,变量的值就等于undefinded;
不完全解构,即等号左边的模式,只匹配一部分的等号右边的数组。这种情况下,解构依然可以成功。但是不能全部成功匹配并改变变量的值
结构失败:
let [foo] = []; // foo: undefinded
let [bar, foo] = [1]; // bar :1; foo: undefinded
不完全解构:
let [a, [b], d] = [1, [2, 3], 4];
a // 1
b // 2
d // 4
如果等号的右边不是数组(或者严格地说,不是可遍历的结构,参见《Iterator》一章),那么将会报错。
// 报错
let [foo] = 1;
let [foo] = false;
let [foo] = NaN;
let [foo] = undefined;
let [foo] = null;
let [foo] = {};
2.对象的解构赋值
解构赋值不仅存在于数组,也可使用与对象。
但数组是以索引进行解构,而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。
let { bar, foo } = { foo: 'aaa', bar: 'bbb' };
foo // "aaa"
bar // "bbb"
let { baz } = { foo: 'aaa', bar: 'bbb' };
baz // undefined
… … …
用途:
(1).交换变量的值
let x =1;
let y =2;
[x,y] = [y,x]
(2) 从函数中返回多个值
//返回的是一个数组
function example(){
return [1,2,3];
}
let [a,b,c] = example();
//返回的是一个对象
function example(){
return {
foo:1,
bar:2
};
}
let { foo, bar } = example();
(3).函数参数的定义
//参数是一组有次序的值
function f([x,y,z]){ ... }
f([1,2,3]);
//参数是一组无次序的值
function f({x,y,z}){ ... }
f({z:3,y:2,x:1});
(4).提取JSON数据
解构赋值对提取JSON对象中的数据,尤其有用。
let jsondata = {
id:42,
status:"ok",
data:[867,5309]
}
let {id,status,data:number} = jsondata;
console.log(id,status,number);
(5) 函数参数的默认值
jQuery.ajax = function (url, {
async = true,
beforeSend = function () {},
cache = true,
complete = function () {},
crossDomain = false,
global = true,
// ... more config
} = {}) {
// ... do stuff
};
(6)遍历 Map 结构
任何部署了 Iterator 接口的对象,都可以用for…of循环遍历。Map 结构原生支持 Iterator 接口,配合变量的解构赋值,获取键名和键值就非常方便。
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) {
// ...
}
(7)输入模块的指定方法
加载模块时,往往需要指定输入哪些方法。解构赋值使得输入语句非常清晰。
const { SourceMapConsumer, SourceNode } = require("source-map");
数值的扩展(部分)
1.Number.isFinite(), Number.isNaN()
Number.isFinite()用来检查一个数值是否为有限的(finite),即不是Infinity。
Number.isNaN()用来检查一个值是否为NaN。
//注意 : 如果参数类型不是数值,Number.isFinite()一律返回false。
Number.isFinite(15); // true
Number.isFinite(0.8); // true
Number.isFinite(NaN); // false
Number.isFinite(Infinity); // false
Number.isFinite(-Infinity); // false
Number.isFinite('foo'); // false
Number.isFinite('15'); // false
Number.isFinite(true); // false
//注意 : 如果参数类型不是NaN,Number.isNaN一律返回false
Number.isNaN(NaN) // true
Number.isNaN(15) // false
Number.isNaN('15') // false
Number.isNaN(true) // false
Number.isNaN(9/NaN) // true
Number.isNaN('true' / 0) // true
Number.isNaN('true' / 'true') // true
(2).Number.parseInt(), Number.parseFloat()
ES6 将全局方法parseInt()和parseFloat(),移植到Number对象上面,行为完全保持不变
// ES5的写法
parseInt('12.34') // 12
parseFloat('123.45#') // 123.45
// ES6的写法
Number.parseInt('12.34') // 12
Number.parseFloat('123.45#') // 123.45
这样做的目的,是逐步减少全局性方法,使得语言逐步模块化。
Number.parseInt === parseInt // true
Number.parseFloat === parseFloat // true
(3). Math.trunc()
Math.trunc方法用于去除一个数的小数部分,返回整数部分。
Math.trunc(4.1) // 4
Math.trunc(4.9) // 4
Math.trunc(-4.1) // -4
Math.trunc(-4.9) // -4
Math.trunc(-0.1234) // -0
// 如果传入的参数非数值,那么Math.trunc()内部会显示用Number方法将其转换为数值,再运算
Math.trunc('123.456') // 123
Math.trunc(true) //1
Math.trunc(false) // 0
Math.trunc(null) // 0
函数的扩展
设置形参
在es6之前,如果想为函数的形参设定默认值,需要使用变通的方法:
function log(x,y){
y = y || "world"; //设置形参的默认值
console.log(x,y);
}
log("hello"); //hello world
log("hello","china") //Hello China
log("hello",'') //hello world
es6写法
function log(x,y="world"){
//....
}
function Point(x=0,y=2,z=3){
console.log(x,y,z)
}
Point(); //0 2 3
箭头函数
箭头函数的作用域与外界是保持一致的,其本身并没有产生作用域
var f = v =>v;
// 等价于
var f = function(v){
return v;
};
如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分。
var f = () => 5;
// 等同于
var f = function () { return 5 };
var sum = (num1, num2) => num1 + num2;
// 等同于
var sum = function(num1, num2) {
return num1 + num2;
};
如果箭头函数的代码块部分多于一条语句,就需要用大括号将他们括起来,必须在对象外面加上大括号,否则会报错。
//报错
let getTemp = (item1,item2)=>{id:item1;name:item2}
//不报
let getTemp = (item1,item2)=>({id:item1;name:item2})
数组扩展
- 扩展运算符
1.扩展运算符
扩展运算符(spread)是三个点(…). 好比rest参数的逆运算,将一个数组转为用逗号分隔的参数序列。
console.log(1,2,[3,4,5],6) // 1,2,[3,4,5],6
console.log(1,2,...[3,4,5],6) // 1,2,3,4,5,6
[...document.querySelectorAll('div')] // [<div>, <div>, <div>]
该运算符主要用于函数调用
function push(array,...items){
array.push(...items)
}
function add(x,y){
return x + y;
}
const numbers = [4,40]
add(...numbers) //44
上述代码中,array.push(…items)和add(…numbers)这两行,都是函数的调用,他们的使用了扩展运算符。该运算符将一个数组,变为参数序列。
扩展运算符与正常的函数参数可以结合使用,非常灵活
function fun(v,w,x,y,z){}
const args = [0,1];
fun(-1,...args,2,...[3]);
//扩展运算符后面还可以放置表达式。
const arr = [
...(x>0?['a']:[]),
'b'
]
//注意,只有函数调用时,扩展运算符才可以放在圆括号中,否则会报错
(...[1,2]) //报错
console.log((...[1,2])) //报错
console.log(...[1,2]) //1 2
上述三种情况,前两种都会报错,因为扩展运算符所在的括号不是函数调用
未完待更
再次感谢 阮一峰 老师的学习资料!
本文记录了一些es6的零碎知识点,由于每一个点都比较分散,因此做了一个汇总。
才疏学浅,不足之处还请批评指正!
startwith 与 endwith
除了 “模板字符串” 伴随着es6的到来,这两个api也一并来到。
先说说他们的用法:
//startwith
let mystr = "Today is Thuseday";
console.log(mystr.startwith("Today ")) //true
console.log(mystr.startwith("today ")) //false
console.log(mystr.endwith("Thuseday")) //true
console.log(mystr.endwith("thuseday")) //false
显而易见,这两个函数有点正则匹配的意思,看起来有点鸡肋。
但实际情况中有时候是个好帮手。
EG:我们需要判断某个网址的协议时候:
let myweb = "http://www.mycom.com"
if(myweb.startsWith('http')){
console.log('this is http');
}else if(myweb.startsWith('https')){
console.log('this is https');
}else if(myweb.startsWith('ws')){
console.log('this is websocket');
}
在我们需要判断文件后缀的时候
const filename = "upload.jpg";
if(filename.endsWith('.jpg')){
console.log('this is jpg file');
}else if(filename.endsWith('.png')){
console.log('this is png file');
}else if(filename.endsWith('.webp')){
console.log('this is webp file');
} //this is jpg file
待更