let 和 const 变量
let 没有变量提升(var 有), let 必须先声明变量才能使用
使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为 “暂时性死区”
let 声明的变量只在所声明的作用域内有效
不允许在同一个作用内重复声明(let) 变量 ;
for 循环
for 循环,循环 语句是一个作用域,内部的语句是一个作用域
for (let i = 0; i < 3; i++) {
let i = 'abc';
console.log(i);
}
输出: // abc
// abc
// abc
见解: 内部作用域的i 和循环的 i 不在一个作用域内,所以不影响
es5 没有块级作用域
- es5 没有块级作用域,只有函数作用域和全局作用域
影响: 内层的变量会影响(覆盖)外部的变量
var tmp = new Date();
function f() {
console.log(tmp);
if (false) {
var tmp = 'hello world';
}
}
f(); // undefined
见解 : 在函数作用域内 ,( tmp = new Date();在函数外,所以没有指向一个地址)
因为var 有变量提升
所以在没有执行if 语句的给tmp赋值之前,tmp = undefined;
所以输出 undefined ,则覆盖了外部的 tmp ;
循环体计数的变量泄露为全局变量
var s = 'hello';
for (var i = 0; i < s.length; i++) {
console.log(s[i]);
}
console.log(i); // 5
见解: i 本身应该属于 循环体内,但是没有跨级作用域,所以 i 变成全局变量
es5 可以可以再块级作用域中声明函数吗,在外部引用是对的
es6 的块级作用域
let 变量使得有了块级作用域
一个{}内属于一个作用域
内层的作用域,和外层作用域可以定义相同的变量
es6 不可以再块级作用域中声明函数,在外部引用是有错误的
( 但是浏览器可以有自己的行为:
允许在块级作用域内声明函数
函数声明会用变量提升原则(提升到全局作用域的头部)
同时函数提升在会提升到所在块级作用域的头部
es6 函数声明在块级作用域内有效在只有使用大括号的前提下成立
// 不报错
'use strict';
if (true) {
function f() {}
}
// 报错
'use strict';
if (true)
function f() {}
do 表达式
可以使得块级作用域有返回值 ,在块级作用域前加上 do ,使得变成 do 表达式
let x = do {
let t = f();
t * t + 1;
};
见解 : X 会得到整个块级作用域的返回值
const 变量 (不变的象征)
本质上 不是const (声明的常量)变量的值不可改变,而是所指向的内存地址不可改变
对于简单的数据类型 (数值,字符串,布尔值) ,只是指向那个变量所存的地址
但是对于复合类型(对象和数组) ,只是能保证指针指向 不变 但是没有办法保证数据
结构不变(就是给对象添加属性等)
const foo = {};
// 为 foo 添加一个属性,可以成功
foo.prop = 123;
foo.prop // 123
// 将 foo 指向另一个对象,就会报错
foo = {}; // TypeError: "foo" is read-only
见解 : 指针指向不变(地址),但是对象本身是可变的
const a = [];
a.push('Hello'); // 可执行
a.length = 0; // 可执行
a = ['Dave']; // 报错
见解 : 指针指向不变(地址),但是对象本身是可变的,但是如果将一个数组赋值给 a 则是错误的;
声明常量必须非常小心
意味着 一旦某个变量使用 const声明 ,则 必须直接在声明的同时赋值
不然是不可以之后赋值的(一定意义上的修改)
和 let 一样,只在块级作用域内有效
没有变量提升 ,也存在暂时性死区(在变量没有声明之前是不可以使用的)
不可重复声明变量(和 let 一样)
对象冻结
Object.freeze
const foo=object.freeze({});
foo.prop=123;
//常规模式上一句不起作用
// 严格模式下,会报错
冻结对象以及冻结对象的属性
** 将对象彻底冻结的函数。 **
var constantize = (obj) => {
Object.freeze(obj);
Object.keys(obj).forEach( (key, i) => {
if ( typeof obj[key] === 'object' ) {
constantize( obj[key] );
}
});
};
见解 :声明函数constantize ,指向 obj ,先是冻结对象,然后利用循环冻结对象属性(调用函数)
声明变量
es5 声明变量
只有两种办法 var 和 function
es6声明变量
var 和function ,let ,const,import,class
顶层对象的属性(浏览器 :window; Node :global)
es5
顶层对象和全局变量一样
es6
var ,window声明的变量属于顶层对象,但是const,let ,class,属于局部变量var a = 1; // 如果在Node的REPL环境,可以写成global.a // 或者采用通用方法,写成this.a window.a // 1 let b = 1; window.b // undefined
见解:顶层对象(全局变量和局部变量在分解 开)
global 对象
因为各个语言不同的区别 目前使用 this 取到顶层对象
在全局环境中this 指向 顶层对象,但是this 一般返回的是所在模块的对象
函数中的this ,如果不是以对象方法运行,而是只是以函数运行,this 指向顶层对象,严格模式下为 undefined;
无论是在严格模式下,还是普通模式下,new function(‘return this’)();总是返回全局对象
但是如果浏览器使用了 CSP(安全模式),eval(),new function()方法都无法使用
自己认为是因为(是防止网络非法攻击)
很难找到一种方法,可以在所有情况下,都取到顶层对象。下面是两种勉强可以使用的方法
(typeof window != 'undefined '
? window
:(typeof process === 'object' &&
typeof require === 'object' &&
typeof global==='object')
? global
:this);
** 见解 :检测是window 对象是否等于undefined,是的话取window
,反之检测是不是属于以下的三种类型,属于的话
,取global 对象,反之取this 对象 **
数组的解构赋值
简单地来说就是模式匹配一致的情况下进行赋值
完全解构
let [foo, [[bar], baz]] = [1, [[2], 3]]; foo // 1 bar // 2 baz // 3 //完全解构
不完全解构
let [x, y] = [1, 2, 3]; x // 1 y // 2 //不完全解构
解构不成功
let [foo] = []; let [bar, foo] = [1]; //解构不成功 //foo的值为undefined
使用set,使用数组方式 解构
Set是ES6引入的集合类,具备Iterator(迭代器)接口,可以用于解构赋值
let [x,y,z] = new set([‘a’,’b’,’c’]);
//x=a,y=b,z=c;
let foo={};
// 空对象不具备Iterator接口
</code></pre>
具有 迭代器的数据结构,都可以采用数组解析的方法赋值
// Generator是ES6引入的函数类型,具备Iterator接口,可以用于解构赋值 // yield在Generator中相当于return,后续文章会详细说明。 function* fibs() { var a = 0, b = 1; while (true) { yield a; [a, b] = [b, a + b]; // 函数内部的解构赋值。 } } let [first, second, third, fourth, fifth, sixth] = fibs(); // sixth: 5
解构赋值允许默认值
let [x, y = 'b'] = ['a']; // x='a', y='b' let [x, y = 'b'] = ['a', undefined]; // x='a', y='b
但是在 es6中使用严格相等运算符(===),所以如果所赋值不是等于 undefined ,则默认值不算
<pre><code>
let [x = 1] = [undefined];
x // 1
let [x = 1] = [null];
x // null
</code></pre>
** 默认值是表达式 **
<pre><code>
function f() {
console.log('aaa');
}
let [x = f()] = [1];
//当1 == udefined时才会计算f();
//否则直接取1(惰性赋值)
</code></pre>
** 默认值是变量 (前提是变量已经声明过)**
对象的默认解构
对象的解构不是像数组解构一样具有次序,是没有次序的,所以在解构赋值的同时应该保证变量必须与属性同名
let { bar, foo } = { foo: "aaa", bar: "bbb" }; foo // "aaa" bar // "bbb" let { baz } = { foo: "aaa", bar: "bbb" }; baz // undefined
如果变量名和属性名不同名 必须写成下面那样
var { foo: baz } = { foo: 'aaa', bar: 'bbb' }; baz // "aaa" let obj = { first: 'hello', last: 'world' }; let { first: f, last: l } = obj; f // 'hello' l // 'world'
解构赋值的变量都会重新声明,所以使用let 和 const 变量进行解构赋值时应该注意,不要提前声明变量
let tmp; let{foo:tmp} = {foo:'aaa'};//SyntaxError: Duplicate declaration "tmp"; //重复声明变量的错误 let foo; ({foo} = {foo: 1}); // 成功 let baz; ({bar: baz} = {bar: 1}); // 成功 //解析器会将 第一个大括号理解成为代码块 所以就是不同的作用域 就不存在重复定义
解构允许嵌套结构的对象 和 数组一样。
let obj = { p: [ 'Hello', { y: 'World' } ] }; let { p: [x, { y }] } = obj; x // "Hello" y // "World"
也具有默认值解构赋值 (和数组一样)
对象的解构赋值,可以很方便地将现有对象的方法,赋值到某个变量
let { log, sin, cos } = Math; //使得获得Math对象的对数、正弦、余弦三个方法,
由于数组本质是特殊的对象,因此可以对数组进行对象属性的解构。
let arr = [1, 2, 3]; let {0 : first, [arr.length - 1] : last} = arr; first // 1 last // 3
字符串的解构赋值
字符串也可以解构赋值。这是因为此时,字符串被转换成了一个类似数组的对象。
数值和布尔值解构赋值
解构赋值时,如果等号右边是数值和布尔值,则会先转为对象。
let {toString: s} = 123; s === Number.prototype.toString // true let {toString: s} = true; s === Boolean.prototype.toString // true
见解 :数值和布尔值的包装对象都有toString属性,因此变量s都能取到值。
解构赋值的规则是,只要等号右边的值不是对象或数组,就先将其转为对象。由于undefined和null无法转为对象,所以对它们进行解构赋值,都会报错。
let { prop: x } = undefined; // TypeError let { prop: y } = null; // TypeError
函数参数的解构赋值
基本和数值解构一样
<pre><code>
[[1, 2], [3, 4]].map(([a, b]) => a + b);
// [ 3, 7 ]
</code></pre>
<pre><code>
function move({x = 0, y = 0} = {}) {
return [x, y];
}
move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, 0]
move({}); // [0, 0]
move(); // [0, 0]
//为变量x和y指定默认值
</code></pre>
<pre><code>
function move({x, y} = { x: 0, y: 0 }) {
return [x, y];
}
move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, undefined]
move({}); // [undefined, undefined]
move(); // [0, 0]
//为函数move的参数指定默认值
</code></pre>
圆括号问题
不允许使用圆括号的情况
在声明语句中
在函数参数中
赋值语句中,不能将整个模式,或嵌套模式中的一层,放在圆括号之中。
就是一边使用圆括号,一边不使用圆括号
// 全部报错 ({ p: a }) = { p: 42 }; ([a]) = [5];
可以使用圆括号的情况
- 赋值语句的非模式部分,可以使用圆括号(就是不存在属相名:属性值)这种的
变量解构赋值的用途
交换变量的值
从函数返回多个值
函数只能返回一个值,如果要返回多个值,只能将它们放在数组或对象里返回。有了解构赋值,取出这些值就非常方便。
// 返回一个数组 function example() { return [1, 2, 3]; } let [a, b, c] = example(); // 返回一个对象 function example() { return { foo: 1, bar: 2 }; } let { foo, bar } = example();
函数参数的定义(就是函数的参数可以是多个解构对象,并且可以很清晰的了解每个变量的赋值)
// 参数是一组有次序的值 function f([x, y, z]) { ... } f([1, 2, 3]); // 参数是一组无次序的值 function f({x, y, z}) { ... } f({z: 3, y: 2, x: 1});
提取 json 数据
<pre><code> let jsondata = { id: 1, status : 1, date :[100,200] }; let {id,status,date:number} = jsondata; //id:1 //status : 1 // number :[100,200] </code></pre>
遍历 map 解构
具有迭代器接口的对象都可以使用 for of 循环得到,健值和 value ,所以
可以很方便
字符串
允许unicode编码
js 内部编码是以 utf - 16存储的,每个字符规定2 个 字节
所以使用