let
参考文档https://es6.ruanyifeng.com/
声明变量
-
变量不能同时重复声明
let star = '罗志祥';
let star = '小猪'
//报错 -
块级作用域
为什么需要块级作用域
第一种场景,内层变量可能会覆盖外层变量。
var tmp = new Date();
function f() {
console.log(tmp);
if (false) {
var tmp = 'hello world';
}
}
f(); // undefined上面代码的原意是,
if代码块的外部使用外层的tmp变量,内部使用内层的tmp变量。但是,函数f执行后,输出结果为undefined,原因在于变量提升,导致内层的tmp变量覆盖了外层的tmp变量。第二种场景,用来计数的循环变量泄露为全局变量。
var s = 'hello';
for (var i = 0; i < s.length; i++) {
console.log(s[i]);
}
console.log(i); // 5上面代码中,变量
i只用来控制循环,但是循环结束后,它并没有消失,泄露成了全局变量。let实际上为 JavaScript 新增了块级作用域。function f1() {
let n = 5;
if (true) {
let n = 10;
}
console.log(n); // 5
}上面的函数有两个代码块,都声明了变量
n,运行后输出 5。这表示外层代码块不受内层代码块的影响。如果两次都使用var定义变量n,最后输出的值才是 10。ES6 允许块级作用域的任意嵌套。
{{{{
{let insane = 'Hello World'}
console.log(insane); // 报错
}}}};上面代码使用了一个五层的块级作用域,每一层都是一个单独的作用域。第四层作用域无法读取第五层作用域的内部变量。
内层作用域可以定义外层作用域的同名变量。
{{{{
let insane = 'Hello World';
{let insane = 'Hello World'}
}}}};块级作用域的出现,实际上使得获得广泛应用的匿名立即执行函数表达式(匿名 IIFE)不再必要了。
// IIFE 写法
(function () {
var tmp =块级作用域中函数的声明
function f() { console.log('I am outside!'); }
(function () {
if (false) {
// 重复声明一次函数f
function f() { console.log('I am inside!'); }
}
f();
}());如果在es5中,控制台中会输出I am inside!,因为if内声明的函数会被提升到函数头部,实际运行的代码如下
// ES5 环境
function f() { console.log('I am outside!'); }
(function () {
function f() { console.log('I am inside!'); }
if (false) {
}
f();
}());如果在es6中,es6引入了块级作用域,明确允许在块级作用域中声明函数,类似于let,在块级作用域之外不可以引用,理论上会得到I am outside!但是,实际上是会报错的,如果改变了块级作用域内声明的函数的处理规则,显然会对老代码产生很大影响。为了减轻因此产生的不兼容问题,ES6 在
-
允许在块级作用域内声明函数。
-
函数声明类似于
var,即会提升到全局作用域或函数作用域的头部。 -
同时,函数声明还会提升到所在的块级作用域的头部。
// 浏览器的 ES6 环境
function f() { console.log('I am outside!'); }
(function () {
var f = undefined;
if (false) {
function f() { console.log('I am inside!'); }
}
f();
}());
// Uncaught TypeError: f is not a function考虑到环境导致的行为差异太大,应该避免在块级作用域内声明函数。如果确实需要,也应该写成函数表达式,而不是函数声明语句。
// 块级作用域内部的函数声明语句,建议不要使用
{
let a = 'secret';
function f() {
return a;
}
}
// 块级作用域内部,优先使用函数表达式
{
let a = 'secret';
let f = function () {
return a;
};
}另外,还有一个需要注意的地方。ES6 的块级作用域必须有大括号,如果没有大括号,JavaScript 引擎就认为不存在块级作用域。
// 第一种写法,报错
if (true) let x = 1;
// 第二种写法,不报错
if (true) {
let x = 1;
}上面代码中,第一种写法没有大括号,所以不存在块级作用域,而
let只能出现在当前作用域的顶层,所以报错。第二种写法有大括号,所以块级作用域成立。函数声明也是如此,严格模式下,函数只能声明在当前作用域的顶层。
// 不报错
'use strict';
if (true) {
function f() {}
}
// 报错
'use strict';
if (true)
function f() {} -
-
不存在变量提升
console.log(song);
let song = '恋爱达人';
//报错 -
不影响作用域链
{
let school = '科师';
function fn(){
console.log(school);
}
fn();
}
//输出科师-
暂时性死区
只要块级作用域存在let命令,他所生命的变量就绑定这个区,不受外部影响
var tmp=123;
if(true) {
tmp= abc;//REferenceError,在此处赋值
let tmp;
}ES6中,如果代码块用存在let或者const命令,这些区块对这些命令生成的变量,从一开始就形成了封闭的作用域。凡是let或者const声明之前使用些变量就会报错
上述代码,存在全局变量tmp,但是块级作用域中let又声明了一个局部变量tmp,导致let绑定了这个块级作用域,所以let声明之前,对tmp赋值都会报错
总之,在代码块内,使用
let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)if (true) {
// TDZ开始
tmp = 'abc'; // ReferenceError
console.log(tmp); // ReferenceError
let tmp; // TDZ结束
console.log(tmp); // undefined
tmp = 123;
console.log(tmp); // 123
}在let之前声明变量tmp之前,都属于变量tmp的死区
typeof在let声明之前使用也会报错
一些隐蔽的死区
function bar(x = y, y = 2) {
return [x, y];
}
bar(); // 报错因为x默认值等于另一个参数y,而此时y还没有声明,属于死区。如果y的默认值为x则不会,因为x已经声明了
// 不报错
var x = x;
// 报错
let x = x;
// ReferenceError: x is not defined使用
let声明变量时,只要变量在还没有声明完成前使用,就会报错。上面这行就属于这个情况,在变量x的声明语句还没有执行完成前,就去取x的值,导致报错”x 未定义“
-
const声明常量
//声明常量
const A = '张三';
-
一定要赋初始值
const A = '张三';
-
一般常量使用大写(潜规则,如果是小写也不报错)
-
常量的值不能修改
-
块儿级作用域
{
const PLAYER = 'UZI'
}
同样存在暂时性死区,只能在生命的位置后面使用
-
对于数组和对象元素的修改不算做对常量的修改,不会报错(因为所指向数组和对象的地址未发生改变)
本质:
const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心如果真的想将对象冻结,应该使用
Object.freeze方法。const foo = Object.freeze({});
// 常规模式时,下面一行不起作用;
// 严格模式时,该行会报错
foo.prop = 123;上面代码中,常量
foo指向一个冻结的对象,所以添加新属性不起作用,严格模式时还会报错。除了将对象本身冻结,对象的属性也应该冻结。下面是一个将对象彻底冻结的函数。
var constantize = (obj) => {
Object.freeze(obj);
Object.keys(obj).forEach( (key, i) => {
if ( typeof obj[key] === 'object' ) {
constantize( obj[key] );
}
});
}; -
不可重复声明
顶层对象的属性
顶层对象,在浏览器环境指的是window对象,在 Node 指的是global对象。ES5 之中,顶层对象的属性与全局变量是等价的。
window.a = 1;
a // 1
a = 2;
window.a // 2
这种情况出现的问题:没法在编译时就报出变量未声明的错误,只有在运行时才知道。程序员容易不知不觉创建了全局变量。顶层对象的属性是可以到输出写的,不利于模块化开发。window对象是有实体含义的,顶层对象是一个有含义的 对象也不合适
es为了保持兼容性,保持兼容性,var命令和function命令声明的全局变量,依旧是顶层对象的属性;另一方面规定,let命令、const命令、class命令声明的全局变量,不属于顶层对象的属性。也就是说,从 ES6 开始,全局变量将逐步与顶层对象的属性脱钩。
var a = 1;
// 如果在 Node 的 REPL 环境,可以写成 global.a
// 或者采用通用方法,写成 this.a
window.a // 1
let b = 1;
window.b // undefined
globalThis对象
JavaScript 语言存在一个顶层对象,它提供全局环境(即全局作用域),所有代码都是在这个环境中运行。但是,顶层对象在各种实现里面是不统一的。
-
浏览器里面,顶层对象是
window,但 Node 和 Web Worker 没有window。 -
浏览器和 Web Worker 里面,
self也指向顶层对象,但是 Node 没有self。 -
Node 里面,顶层对象是
global,但其他环境都不支持。
同一段代码为了能够在各种环境,都能取到顶层对象,现在一般是使用this关键字,但是有局限性。
-
全局环境中,
this会返回顶层对象。但是,Node.js 模块中this返回的是当前模块,ES6 模块中this返回的是undefined。 -
函数里面的
this,如果函数不是作为对象的方法运行,而是单纯作为函数运行,this会指向顶层对象。但是,严格模式下,这时this会返回undefined。 -
不管是严格模式,还是普通模式,
new Function('return this')(),总是会返回全局对象。但是,如果浏览器用了 CSP(Content Security Policy,内容安全策略),那么eval、new Function这些方法都可能无法使用。
综上所述,很难找到一种方法,可以在所有情况下,都取到顶层对象。下面是两种勉强可以使用的方法。
// 方法一
(typeof window !== 'undefined'
? window
: (typeof process === 'object' &&
typeof require === 'function' &&
typeof global === 'object')
? global
: this);
// 方法二
var getGlobal = function () {
if (typeof self !== 'undefined') { return self; }
if (typeof window !== 'undefined') { return window; }
if (typeof global !== 'undefined') { return global; }
throw new Error('unable to locate global object');
};
垫片库
变量的解构赋值
es6允许按照一定模式从数组和对象中提取值,对变量进行复制,这被称为解构赋值
数组的解构
const F4 = ['小沈阳', '刘能', '赵四', '宋小宝'];
let [xiao, liu, zhao, song] = F4;
console.log(xiao);
console.log(liu);
console.log(zhao);
console.log(song);
let [head, ...tail] = [1, 2, 3, 4];
head // 1
tail // [2, 3, 4]
另一种情况是不完全解构,即等号左边的模式,只匹配一部分的等号右边的数组。这种情况下,解构依然可以成功。
let [x, y] = [1, 2, 3];
x // 1
y // 2
let [a, [b], d] = [1, [2, 3], 4];
a // 1
b // 2
d // 4
如果等号右边不是数组,那么会报错
对象的解构
const ZHAO = {
name: '赵本山',
age: '不详',
xiaopin: function () {
console.log("我可以 演小品");
}
}
let { name, age, xiaopin } = ZHAO;
console.log(name);
console.log(age);
console.log(xiaopin);
xiaopin();
如果以后遇到方法频繁调用,可以想到解构赋值
用法
对象的解构赋值,可以很方便地将现有对象的方法,赋值到某个变量。
// 例一
let { log, sin, cos } = Math;
// 例二
const { log } = console;
log('hello') // hello
上面代码的例一将Math对象的对数、正弦、余弦三个方法,赋值到对应的变量上,使用起来就会方便很多。例二将console.log赋值到log变量。
也就是说,对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,而不是前者。
let { foo: baz } = { foo: 'aaa', bar: 'bbb' };
baz // "aaa"
foo // error: foo is not defined
上面代码中,foo是匹配的模式,baz才是变量。真正被赋值的是变量baz,而不是模式foo。
注意
(1)如果要将一个已经声明的变量用于解构赋值,必须非常小心。
// 错误的写法
let x;
{x} = {x: 1};
// SyntaxError: syntax error
上面代码的写法会报错,因为 JavaScript 引擎会将{x}理解成一个代码块,从而发生语法错误。只有不将大括号写在行首,避免 JavaScript 将其解释为代码块,才能解决这个问题。
// 正确的写法
let x;
({x} = {x: 1});
上面代码将整个解构赋值语句,放在一个圆括号里面,就可以正确执行。关于圆括号与解构赋值的关系,参见下文。
(2)解构赋值允许等号左边的模式之中,不放置任何变量名。因此,可以写出非常古怪的赋值表达式。
({} = [true, false]);
({} = 'abc');
({} = []);
上面的表达式虽然毫无意义,但是语法是合法的,可以执行。
(3)由于数组本质是特殊的对象,因此可以对数组进行对象属性的解构。
let arr = [1, 2, 3];
let {0 : first, [arr.length - 1] : last} = arr;
first // 1
last // 3
上面代码对数组进行对象解构。数组arr的0键对应的值是1,[arr.length - 1]就是2键,对应的值是3。方括号这种写法,属于“属性名表达式”
字符串的解构赋值
字符串也可以解构赋值。这是因为此时,字符串被转换成了一个类似数组的对象。
const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"
类似数组的对象都有一个length属性,因此还可以对这个属性解构赋值。
let {length : len} = 'hello';
len // 5
数值和布尔值的解构赋值
解构赋值时,如果等号右边是数值和布尔值,则会先转为对象。
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
函数的解构赋值
函数的参数也可以使用解构赋值。
function add([x, y]){
return x + y;
}
add([1, 2]); // 3
上面代码中,函数add的参数表面上是一个数组,但在传入参数的那一刻,数组参数就被解构成变量x和y。对于函数内部的代码来说,它们能感受到的参数就是x和y。
下面是另一个例子。
[[1, 2], [3, 4]].map(([a, b]) => a + b);
// [ 3, 7 ]
函数参数的解构也可以使用默认值。
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]
上面代码中,函数move的参数是一个对象,通过对这个对象进行解构,得到变量x和y的值。如果解构失败,x和y等于默认值。
注意,下面的写法会得到不一样的结果。
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的参数指定默认值,而不是为变量x和y指定默认值,所以会得到与前一种写法不同的结果。
undefined就会触发函数参数的默认值。
[1, undefined, 3].map((x = 'yes') => x);
// [ 1, 'yes', 3 ]
模板字符串
ES6引入新的声明字符串的方式 ``
-
声明
let str = `我也是一个字符串`;
console.log(str, typeof str); -
内容中可以直接出现换行符
let str = `<ul>
<li>沈腾</li>
<li>魏翔</li>
</ul>`; -
变量拼接
let str = `魏翔`;
let out = `${str}是我心中最搞笑的演员!`
console.log(out);
简化对象写法
ES6允许在打括号里面直接写入变量和函数,作为对象的属性和方法,这样书写更加简洁
let name = '张三';
let like = function () {
console.log("吃饭");
}
const SCHOLL = {
name,
like,
improve() {
console.log("吃两碗");
}
}
console.log(SCHOLL);
箭头函数
ES6中允许使用箭头(=>)定义函数
格式
//声明一个函数
let fn = (a,b) => {
return a+b;
}
//调用函数
let result = fn(1,2);
console.log(result);
//3
箭头函数的特性
-
this是静态的,this始终指向函数声明时所在作用域下的this的值
//es5
function getName(){
console.log(this.name);
}
//es6中箭头函数
let getName2= ()=>{
console.log(this.name);
}
//设置window对象的name属性
window.name = '张三';
const SCHOOL = {
name:"李四";
}
//直接调用
getName();//张三,普通函数直接调用this指向window
getName2();//张三,是在全局作用域下声明的
//call方法调用(call方法可以改变this指向)
getName.call(SCHOOL);//李四
getName2.call(SCHOOL);//张三,因为是静态的所以不改变this指向 -
不能作为构造实例化对象
let Person = (name,age) =>{
this.name=name;
this.age=age;
}
let me = new Person('张三',30);
console.log(me);
//TypeError: Person is not a constructor(构造器) -
不能死用arguments变量
let fn = () => {
console.log(arguments);
}
fn(1,2,3); -
箭头函数的简写
1)省略小括号,当形参有且只有 一个的时候
let add = n =>{
return n+n;
}
console.log(add(9));//182)省略花括号,当代码只有一条语句时,此时return必须省略,而且语句的执行结果就是函数的返回值
let pow = n => n * n;
console.log(pow(8));//64注意:
-
箭头函数适合与this无关的回调,定时器,数组的方法回调
-
箭头不适合与this有关的回调,事件回调,对象的方法
-
函数参数默认值
ES6 允许给函数参数赋初始值
-
形参初始值 具有默认值的参数,一般为只要靠后(潜规则)
function add (a,b,c=10){
return a + b +c;
}
let result = add(1,2);
console.log(restult); -
与解构赋值结合
function connect({host="127.0.0.1",username,password,port}){
console.log(host);
console.log(username);
console.log(password);
console.log(port);
}
connect({
host:'www.example.com',
username:'root',
password:'root',
port:3306
})rest参数
ES6引入rest参数,用于获取函数的实参,用来代替arguments
//ES5获取方式
function date(){
console.log(arguments);
}
date('柏芝','阿娇','思慧');//rest参数
function date(...args){
console.log(args);
}
date('柏芝','阿娇','思慧');
扩展运算符
...扩展运算符能将数组转换为逗号分隔的参数序列
//声明一个数组
const tfboys = ['易烊千玺','王源','王俊凯'];
//=>'易烊千玺','王源','王俊凯'
//声明一个函数
function chunwan (){
console.log(arguments);
}
chunwan(...tfboys);//chunwan('易烊千玺','王源','王俊凯')
扩展运算符的应用
//1.数组的合并
const kuaizi =['wangtaili','xiaoyang'];
const fenghuang = ['zengyi','linghua'];
//ES5
//const zuixuanxiaopingguo = kuaizi.concat(fenghuang);
//扩展运算符
const zuixuanxiaopingguo = [...kuaizi,...fenghuang];
console.log(zuixuanxiaopingguo);
// 2.数组克隆(浅拷贝)
const sanzhihua = ['E','G','M'];
const sanzhicao = [...sanzhihua];
console.log(sanzhicao);//['E','G','M']
//3.将伪数组转换为真正的数组
const divs = document.querySelectorAll('div');
const divArr = [...divs];
console.log(divArr);
//也可以将arguments转化成数组,但是因为有rest参数,所以没必要进行这步骤操作
symbol基本使用
ES引入了一种新的数据类型Symbol,表示独一无二的值,是一种类似于字符串的数据类型,
Symbol特点
1)Symbol的值是唯一的,用来解决命名冲突
2)Symbol值不能与其他数据进行运算
3)Symbol定义的对象属性不能使用for...in循环遍历,但是可以使用RefLect.ownKeys来获取对象的所有键名
//创建Symbol
let s = Symbol();
let s2 = Symbol('张三');
let s3 = Symbol('张三');
//s2===s3 不相等
let s4 = Symbol.for('张三');
let s5 = Symbol.for('张三');
//s4===s5相等
//不能与其他数据进行运算
let result = s+100;
let result = s>100;
let result = s+s;
//报错
Symbol.for()与Symbol()这两种写法都会生成新的Symbol。
他们的区别是前者会被登记在全局环境中供搜索,后者不会。
Symbol创建对象属性
给对象添加属性和方法
//声明一个对象
let game = {
name: '俄罗斯方块',
up: function () { },
down: function () { }
}
let methods = {
up: Symbol(),
down: Symbol()
}
game[methods.up] = function () {
console.log("我可以改变形状!");
}
game[methods.down] = function () {
console.log("我可以快速下降!");
}
console.log(game);
let zibao = Symbol('zibao');
//只能通过这个调用
let youxi = {
name: '狼人杀',
[Symbol('say')]: function () {
console.log("我可以发言了");
},
//调用不到
[zibao]: function () {
console.log("我可以自爆");
}
}
console.log(youxi)
//youxi[zibao]
迭代器
迭代器是一种接口,为不同的数据结构提供统一的访问机制。任何数据结构只要部署Iterator接口,就可以完成遍历操作。
1)ES6创造了一种新的遍历命令for...of循环,Iterator接口主要供for...of消费
2)原生具有iterator接口的数据(可用 for of 遍历)
a)Array
b) Arguments
c) Set
d) Map
e) String
f) TypedArray
g) NodeList
const xiyou = ['tangseng', 'sunwukong', 'zhubajie', 'shaseng'];
for (let i of xiyou) {
console.log(i);
}
//for of 中变量表示的是键值
//for in 中变量表示的是键名
3)工作原理
a)创建一个指针对象,指向当前数据结构的起始位置
b)第一次调用对象的next方法,指针自动指向数据结构的第一个成员
c)接下来不断调用next方法,指针一直往后移动,直到指向最后一个成员
d)每调用next方法返回一个包含value和 done属性的对象
const xiyou = ['tangseng', 'sunwukong', 'zhubajie', 'shaseng'];
let iterator = xiyou[Symbol.iterator]();
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
注意:需要自定义遍历数据的时候,要想到迭代器
const banji = {
name: '终极一班',
stus: [
'ming',
'ning',
'tian'
],
//创建一个iterator对象
[Symbol.iterator]() {
//声明一个索引数
let index = 0;
return {
next: () => {
if (index < banji.stus.length) {
const result = { value: banji.stus[index], done: false }
//索引数自增
index++;
return result;
} else {
return { value: undefined, done: true };
}
}
}
}
}
for (let i of banji) {
console.log(i);
}
生成器
生成器函数是ES6提供的一种异步编程解决方案,语法行为与传统函数完全不同
//生成器其实就是一个特殊的函数
function* gen() {
console.log('hello generator')
}
let iterator = gen();
iterator.next();
需要调用迭代器中的next方法才能执行
function* gen() {
console.log('111');
yield '一直没有耳朵';
console.log('222');
yield '一直没有尾巴';
console.log('333');
yield '真奇怪真奇怪';
console.log('444');
}
let iterator = gen();
iterator.next();
iterator.next();
iterator.next();
iterator.next();
//三个yield把gen分成四份,开始到耳朵,耳朵到尾巴,尾巴到奇怪,奇怪到最后
function* gen() {
// console.log('111');
yield '一直没有耳朵';
// console.log('222');
yield '一直没有尾巴';
// console.log('333');
yield '真奇怪真奇怪';
// console.log('444');
}
let iterator = gen();
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
生成器函数参数
function* gen(arg) {
console.log(arg);
let one = yield 111;
console.log(one);
let two = yield 222;
console.log(two);
let three = yield 333;
console.log(three);
}
let iterator = gen('AAA');
console.log(iterator.next('BBB'));
console.log(iterator.next('CCC'));
console.log(iterator.next('DDD'));
整体的函数传参没有问题,next中也可以传参,是作为上一个yield的参数
例子
function getUsers() {
setTimeout(() => {
let date = 'renyuanxinxi';
siofjiods
iterator.next(date);
}, 1000)
}
function getOrders() {
setTimeout(() => {
let date = 'dingdanshuju';
iterator.next(date);
}, 1000)
}
function getGoods() {
setTimeout(() => {
let date = 'shangpinshuju';
iterator.next(date);
}, 1000)
}
function* gen() {
let users = yield getUsers();
console.log(users);
let orders = yield getOrders();
console.log(orders);
let goods = yield getGoods();
console.log(goods);
}
let iterator = gen();
iterator.next();
Promise
Promise是ES6引入的异步编程的新解决方案。语法上 Promis是一个构造函数,用来封装异步操作并可以获取其成功或失败的结果。
-
Promise构造函数: Promise (excutor){}
-
Promise.prototype.then方法
-
Promise.prototype.catch方法
//实例化一个对象
const p = new Promise(function (resolve, reject) {
//promise中接受一个函数参数,函数中有两个形参
setTimeout(function () {
// let data = '数据库中用户传的数据';
// resolve(data);
let erro = '数据库中信息传输失败';
reject(erro);
}, 1000);
// resolve表示成功,如果成功,调用then方法中函数为value的函数;reject表示失败,如果失败,调用then方法中reason的函数。
});
//调用Promise中的then方法
p.then(function (value) {
console.log(value);
}, function (reason) {
console.error(reason);
})
Promise封装ajax
const p = new Promise(function(resolve,reject){
//1.创建对象
const xhr = new XMLHttpRequest();
//2.初始化
xhr.open('GET','www.example.com');
//3.发送
xhr.send();
//4.绑定事件,处理相应结果
xhr.onreadystatechange = function(){
//判断
if(xhr.readyState === 4&&xhr.status===200){
resolve(xhr.response)
}else{
reject(xhr.status);
}
}
})
p.then(function(value){
console.log(value);
},function(reason){
console.error(reason);
})
Promise—then方法
const p = new Promise((resolve, reject) => {
setTimeout(function () {
let data = '数据传输';
resolve(data);
let err = '失败';
reject(err);
}, 1000)
})
//调用then方法,then方法的返回结果是Promise对象,对象状态由回调函数 的执行结果决定
//1.如果回调函数返回的结果是非promise类型的属性,状态为成功,返回值为对象的值
const result = p.then(value => {
console.log(value);
//非promise类型的属性
return 'iloveyou';
//是promise对象,对象中成功,状态为成功
return new Promise((resolve, reject) => {
//resolve('ok');
reject('error');
})
//抛出错误状态为失败
//throw new Error ('出错啦!');
throw '出错啦';
}, reason => {
console.warn(reason)
})
//链式回调,解决回调地狱
p.then(value => {
}, reason => {
}).then((value) => {
})
catch方法
用来指定promise失败的回调,相当于then方法不指定成功的回调
const p = new Promise((resolve,reject)=>{
setTimeout(()=>{
//设置p对象的状态为失败
reject("出错啦");
},1000)
});
p.catch(function(){
console,warn(reason);
});
Set
ES6提供了新的数据结构Set(集合)。它类似于数组,但成员的值都是唯一的,集合实现了iterator接口,所以可以使用「扩展运算符』和「 for...of...』进行遍历,集合的属性和方法:
-
size 返回集合的元素个数
-
add 增加一个新元素,返回当前集合
-
delete 删除元素,返回boolean值
-
has 检测集合中是否包含某个元素,返回boolean值
-
clear 清空集合
let s = new Set([1,2,3,4,5]);
//元素个数
// console.log(s2.size);
//添加新的元素
// s2.add('6');
//删除元素
// s2.delete('5');//检测
//console.log(s2.has('糟心事'));
//清空
// s2.clear();
// console.log(s2);
//用for of遍历
for( let v of s2){
console.log(v);
}
Ts = new Set([1,2,3,4,5]);
let arr = [1, 2, 3, 4, 5, 4, 3, 2, 1];
//数组去重
// let result = [...new Set(arr)];
// console.log(result);
//...拓展运算符能将数组转换成用逗号连接的参数序列
//交集
//filter可以理解为数组的一个用来过滤的方法,回调里会默认收到三个参数,item(item只是这个参数的标识符)就是其中第一个参数,表示每一次调用回调函数时所传递的元素
let arr2 = [4, 5, 6, 5, 6];
// let result = [...new Set(arr)].filter(item => {
// let s2 = new Set(arr2)
// if (s2.has(item)) {
// return true;
// } else {
// return false;
// }
// })
// let result = [...new Set(arr)].filter(item => new Set(arr2).has(item))
// console.log(result)
//并集
// let union = [...new Set([...arr, ...arr2])];
// console.log(union);
//差集
let diff = [
Map
ES6提供了Map 数据结构。它类似于对象,也是键值对的集合。但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。Map也实现了iterator接口,所以可以使用「扩展运算符』和「 for...of...』进行遍历。Map的属性和方法:
-
size返回Map的元素个数
-
set增加一个新元素,返回当前Map
-
get 返回键名对象的键值
-
has检测Map中是否包含某个元素,返回boolean值
-
clear清空集合,返回undefined
//声明Map
let m = new Map();
//添加元素
m.set('name', '张三');
m.set('change', function () {
console.log("多吃一碗饭");
});
let key = {
school: 'abc'
}
m.set(key, ['a', 'b', 'c']);
console.log(m)
//size
console.log(m.size);
//删除
m.delete('name');
console.log(m);
//获取
console.log(m.get(key));
//也可以用for of遍历
//声明Map
let m = new Map();
//添加元素
m.set('name', '张三');
m.set('change', function () {
console.log("多吃一碗饭");
});
let key = {
school: 'abc'
}
m.set(key, ['a', 'b', 'c']);
for (let i of m) {
console.log(i);
}
class类
ES6提供了更接近传统语言的写法,引入了Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。基本上,ES6的class可以看作只是一个语法糖,它的绝大部分功能,ES5都可以做到,新的 class 写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。
-
class声明类
-
constructor定义构造函数初始化
-
extends继承父类
-
super调用父级构造方法
-
static定义静态方法和属性
-
父类方法可以重写
class Phone {
//构造方法名字不能修改
constructor(brand, price) {
this.brand = brand;
this.price = price;
}
//方法必须使用该语法
call() {
console.log("我可以打电话!");
}
}
let HuaWei = new Phone('华为', 1999);
console.log(HuaWei);
static静态方法
static定义的属性属于class
class Phone {
//静态属性
static name = '手机';
static change() {
console.log("我可以改变世界");
}
}
let HuaWei = new Phone();
console.log(HuaWei.name);
console.log(Phone.name);
class的继承extends
class Phone {
//构造方法名字不能修改
constructor(brand, price) {
this.brand = brand;
this.price = price;
}
//方法必须使用该语法
call() {
console.log("我可以打电话!");
}
}
class smartPhone extends Phone {
constructor(brand, price, color, size) {
super(brand, price)
this.color = color;
this.size = size;
}
photo() {
console.log("我可以拍照!");
}
playGame() {
console.log("我可以玩游戏!");
}
}
let oppo = new smartPhone('oppo', 1000, '红色', '5inch');
console.log(oppo);
子类对父类进行重写
class Phone {
//构造方法名字不能修改
constructor(brand, price) {
this.brand = brand;
this.price = price;
}
//方法必须使用该语法
call() {
console.log("我可以打电话!");
}
}
class smartPhone extends Phone {
constructor(brand, price, color, size) {
super(brand, price)
this.color = color;
this.size = size;
}
//子类对父类进行重写
call() {
console.log("我可以视频通话!")
}
photo() {
console.log("我可以拍照!");
}
playGame() {
console.log("我可以玩游戏!");
}
}
let oppo = new smartPhone('oppo', 1000, '红色', '5inch');
console.log(oppo);
get和set
对属性进行绑定
class Phone {
get price() {
console.log("价格属性被读取了");
return "iloveyou"
}
set price(newVal) {
console.log("价格被修改了");
}
}
let oppo = new Phone();
console.log(oppo.price);
console.log("-----------------------------------");
oppo.price = 'free'
数值扩充
-
Number.EPSILON是JavaScript表示的最小精度 EPSILON属性的值接近于2.2204460492503130808472633361816E-16 如果两个数相减小于这个数那么这个两个数相等
//可以进行浮点数的比较
function equal(a,b){
if(Math.abs(a-b)<Number.ESPSILON){
return ture;
}else{
return false;
}
}
console.log(0.1+0.2===0.3);
console.log(equal(0.1+0.2,0.3)); -
二进制和八进制
二进制0b开头
八进制0o开头
十六进制0x开头
-
Number.isFinite检测一个数值是否为有限数
-
Number.isNaN检测一个数值是否为NaN
-
Number. parseInt Number.parseFloat字符串转整数
-
Number.isInteger判断一个数是否为整数
-
Math.trunc将数字的小数部分抹掉
-
Math.sign判断一个数到底为正数负数还是零
模块化
模块化是指将一个大的程序文件,拆分成许多小的文件,然后将小文件组合起来
模块化的好处
-
防止命名冲突
-
代码复用
-
高维护性
ES6模块化语法
模块功能主要有两个命令构成:export和import。
export命令用于规定模块的对外接口
import命令用于输入其他模块提供的功能
使用ES6模块化
为什么要在服务器端上部署:因为nodeJs支持commonJS规范是后端语言,前端JS不支持commonJS规范
//1.通用的导入方式
import * as m1 from "./js/m1.js";
console.log(school);
import * as m2 from "./js/m2.js";
console.log(m2);
import * as m3 from "./js/m3.js";
console.log(m3);
//像调用default中的属性需要
m3.default.change();
//2.解构赋值形式
import { school, teach } from "./js/m1.js";
console.log(school);
console.log(teach);
import { school as keshi, eat } from "./js/m2.js"
console.log(keshi);
console.log(teach);
import { default as m3 } from "./js/m3.js";
console.log(m3)
//简便形式
import m3 from "./js/m3.js";
console.log(m3);//运行结果与上面一样
//m1.js
export let school = '科师';
export function teach() {
console.log("我们可以教你文章");
}
//m2.js
let school = 'keshi';
function eat() {
console.log('食堂');
}
export { school, eat };
//m3.js
//默认暴露,export default可以是任意类型,可以是对象,数字符串
export default {
school:'keshi';
change:function(){
console.log("我们可以改变世界");
}
}
解构赋值形式
浙公网安备 33010602011771号