class Surname {
constructor(surname) { // 构造函数
this.surname = surname;
}
sayName() {
return this.surname
}
}
上面定义了一个Surname类,它有一个属性surname和一个方法sayName()。
class FirstName extends Surname {
constructor(surname, firstName) { // 构造函数
super(surname); // 调用父类构造函数
this.firstName = firstName;
}
parent(){
return this.sayName()
}
}
接着定义一个FirstName类,使用extends来继承于Surname。注意,必须使用super关键字来引用父类的属性和方法。
var cp = new FirstName("B","everly__");
console.log(cp.surname+cp.firstName);
console.log(cp.parent());
console.log(cp instanceof Surname);
console.log(cp instanceof FirstName);
仔细看上面的四行console.log会输出什么!
() => 1;
// 其实这是一个自执行的匿名函数
(function(){
return 1;
})
当然我们也可以给个参数(注意:参数就参数 ,别给个数字摆在那边~那是参数吗??)
(a) => 1 + a
// 相当于
(function(a){
return 1 + a
})
如果你不想要( )也行,但是只能有一个参数
v => v + 1
// 相当于
(function(v){
return v + 1
})
如果你说我不想要参数,又不想要给它( ),可以吗???扯犊子吧你!
(a,b) => a + b
// 相当于
(function(a, b){
return a + b
})
注意:相当于不是完全等于,箭头函数没有constructor,没有prototype,没有this,所以不支持new操作符,不可以当做构造函数。但是箭头函数对于this的处理和其他函数不一样。箭头函数中的this始终指向函数定义时的this,而非执行的时候。也就是说箭头函数执行时,会向外层函数查找this,直到找到this就拿来为己用!
你可能早就听说过ES6中let那些奇怪的传说,那么什么是块级:块级就是一个{},用let和const定义的变量的作用域会被限制在当前块内。
在ES5中存在一个 很经典的循环事件绑定的问题,我们可以使用数组模拟dom集合来还原这个问题:
var arr = [];
for(var i = 0; i <6; i++){
arr.push(function () {
console.log(i);
});
}
arr[0]();
arr[1]();
arr[2]();
不难理解,arr i 输出的都会是5,因为在ES5中不存在块级作用域的概念,在for循环的括号中声明的变量就像在外面声明的变量那样,每执行一次循环,新的i值就会覆盖旧的i值,导致最后输出的是以后一轮循环的i值。
而在ES6中,我们可以使用let声明变量来处理这个问题。let的用法与var类似,但是其声明的变量只在let命令所在的代码块中有效。
let arr = [];
for(let i = 0; i <6; i++){
arr.push(function () {
console.log(i);
i++;
});
}
arr[0]();
arr[0](); // 注意这里输出1
arr[1]();
arr[2]();
上面的代码中,i只在本轮循环中有效,每一次循环的i其实都是一个新的变量,于是最后输出0,1,1,2。
块级作用域的出现也使得广泛使用的匿名立即执行函数不再必要了。
(function(){
var a = 10;
... ...
}())
// 等价于
{
let a = 10;
}
let的死区:
使用let声明的变量不会出现像var那样存在“变量提升”现象。但本质上,二者是相同的,它们都会在初始化时先初始化属性,再初始化逻辑,然而二者的区别在于使用let声明的变量虽然一开始就存在,但是不能使用,而使用var声明的变量则可以。一定要在声明后使用,否则将会报错。
let foo = 'foo';
if(true){
console.log(foo);
let foo = 'foo bar';
console.log(foo);
}
下面的代码在第一次输出foo的时候会报错,提示foo没有定义,这就是死区效应。
只要块级作用域内存在let命令,它所声明的变量就绑定在这个区域,不再受外部影响。ES6明确规定,只要块级作用域中存在let命令,则这个块区对这些命令声明的变量从一开始就形成封闭的作用域。只要声明之前,使用这些变量,就会报错。这样说有些抽象,你只需要记住:在块级作用域内如果使用let声明了某个变量,那么这个变量名必须在声明它的语句后使用,即使块外部的变量有与之重名的也不行。从块开头到声明变量的语句中间的部分,称为这个变量的“暂时性死区”。
不允许重复声明
let不允许在相同作用域内重复声明同一个变量,即同一个作用域内不允许出现名称相同的变量。
const与let的特性基本相同,但顾名思义,const用于声明常量,一旦声明,必须立即赋值,且以后不可更改。
cosnt foo // 报错,没有立即赋值
const foo = {name:'Zheng Aojin'}
foo.name = 'Jane';
foo.age = 25;
foo = {name:'Kyle Hu'} // 报错,因为改变了引用关系
先来看看Promise长什么样子
var promise = new Promise(function (resolve, reject){
if (success){
return resolve(data);
} else {
return reject(data);
}
});
promise.then(function(data){
console.log(data);
}, function(err){
// deal the err.
})
这里的promise变量是Promise的实例。
Promise对象在new的时候,接受一个回调函数作为参数,在Pomise对象创建的时候回执行回调函数里面的逻辑。
现在来认识两个Promise内的回调函数:resolve、reject,这两个函数什么时候调用由你自己来控制,如上代码,假如success为你的接口获取的数据,当它为真的时候调用resolve,else不用说了。也就是resolve是逻辑成功的回调,reject是捕获异常的回调。在实例化的promise.then(),then接受两个函数式参数,第一个即为resolve,同理反之。有的同学说,这也没什么用啊,我自己写回调也可以。那么请看下面的代码。
promise.then(function(data){
console.log(data) .....................(1)
return data + 'first'
}).then(function(data){
console.log(data) .....................(2)
}).catch(function(err){
console.log(err) .....................(3)
})
此时的Promise就变成了另外一种链式调用的写法,相信聪明的同学已经看出来其中的奥妙了。
在Promise对象链式调用中,.then()即为resolve的回调。Promise的一个特殊性在于,Promise的then方法返回的依然是一个Promise对象。如果同一个resolve获取的数据,要经过处理,可以把处理过得数据return出来,传递给下一个.then()来用,这样上边的代码就不难理解了吧,第二个.then()其实是第一个.then()方法return的Promise对象,所以可以用.then来连接。在一个链式调用结构中,每一个then都返回一个Promise对象,那么每一个函数体内都有两个回调函数resolve和reject,如果是这样,那不又变成回调地狱了。
其实Promise还有另一个方法catch,用这个函数接受一个回调函数来统一处理错误。
现在假设上边的promise 获取的数据为一个字符串’get success’,即 data=’get success’,这样上边的三个console.log输出的结果为:
(1) 'get success'
(2) 'get success first'
(3) 'error'
注意: Promise对象是异步的,什么是异步的呢?就是一个.then()内的逻辑没执行完,不会执行下一个。
控制并发的Promise
Promise有一个”静态方法”——Promise.all(注意并非是promise.prototype), 这个方法接受一个元素是Promise对象的数组。
这个方法也返回一个Promise对象,如果数组中所有的Promise对象都resolve了,那么这些resolve的值将作为一个数组作为Promise.all这个方法的返回值的(Promise对象)的resolve值,之后可以被then方法处理。如果数组中任意的Promise被reject,那么该reject的值就是Promise.all方法的返回值的reject值.
很op的一点是:
then方法的第一个回调函数接收的resolve值(如上所述,是一个数组)的顺序和Promise.all中参数数组的顺序一致,而不是按时间顺序排序。
还有一个和Promise.all相类似的方法Promise.race,它同样接收一个数组,只不过它只接受第一个被resolve的值。
先来看一下以前我们对字符串的使用:
var name = 'CSDN博客';
var desc = 'CSDN深度IT技术博客,移动开发博客,Web前端博客,企业架构博客';
var html = function(name, desc){
var tpl = '公司名:' + name + '\n'+
'简介:'+ desc;
return tpl;
}
而现在:
var html = `公司名:${name}
简介:${desc}`;
在举个例子就明白了:
var x = 1;
var y = 2;
`${ x } + ${ y } = ${ x + y}` // "1 + 2 = 3"
不同于普通字符串,模板字符串还可以多行书写,模板字符串中所有的空格,新行,缩进都会原样的输出在生成的字符串中。
而单纯的模板字符串还存在着很多的局限性。如:
export基本用法:
export function foo() {
// ..
}
export var awesome = 42;
var bar = [1,2,3];
export { bar };
function foo() {
// ..
}
var awesome = 42;
var bar = [1,2,3];
export { foo, awesome, bar };
导出的时候重命名:
function foo() { .. }
export { foo as bar };
默认导出,每个模块只能有一个默认导出:
function foo(..) {
// ..
}
export default foo;
export{ foo as default };
混合默认导出和普通的导出:
function foo() { .. }
function bar() { .. }
function baz() { .. }
export { foo as default, bar, baz, .. };
从其他模块导出:
export { foo, bar } from "baz";
export { foo as FOO, bar as BAR } from "baz";
export * from "baz";
import基本用法
import { foo } from "foo";
foo();
import { foo as theFooFunc } from "foo";
theFooFunc();
import foo from "foo";
// or:
import { default as foo } from "foo";
export default function foo() { .. }
export function bar() { .. }
export function baz() { .. }
import FOOFN, { bar, baz as BAZ } from "foo";
FOOFN();
bar();
BAZ();
export function bar() { .. }
export var x = 42;
export function baz() { .. }
import * as foo from "foo";
foo.bar();
foo.x; // 42
foo.baz();
import有一个hoisted的过程,和var变量提升一样:
foo();
import { foo } from "foo";
ES6允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构。
ES6中对变量赋值可以写成下面的样式。
var [a,b,c] = [1,2,3];
//等同于下列三句
var a = 1;
var b = 2;
var c = 3;
本质上,这种写法属于‘模式匹配’,只要等号两边的模式相同,左边的变量就会被赋予对应的值。
let [foo,[[bar],baz]] = [1,[2],3]];
console.log(foo);//1
console.log(bar);//2
console.log(baz);//3
let [,,third] = ['foo','bar','baz'];
console.log(third)//'baz'
let [head,...tail] = [1,2,3,4];
console.log(head);//1
console.log(tail);//[2,3,4]
//当解构不成功的时候,变量的值为undefined
let [foo] = []; //foo值为undefined
let [bar,foo] = [1]; //foo值为undefined
不完全解构
当等号左边的模式只匹配等号右边数组的一部分时,解构依然可以成功。
let [x,y] = [1,2,3];//x值为1,y值为2
let [a,[b],d] = [1,[2,3],4];//a值为1,b值为2,d值为4
如果等号右边的不是可遍历的结构(数组、对象、Map和Set),那么将会报错。
let [foo] = 1;//报错
默认值
解构赋值允许指定默认值,当等号右边的值为undefined时,设置其值为默认值。这里需要主要,ES6内部使用严格相等运算符(===)判断一个位置是否有值。所以,如果一个数组成员不严格等于undefined,默认值是不会生效的。
var [foo = true] = [];//foo值为true
[x,y = 'b'] = ['a',undefined];//x = 'a',y = 'b'
var [x=1] = [null];//x=null
如果默认值是一个表达式,那么这个表达式是惰性求值的,即只有在用到的时候才会求值。
function f(){
console.log('aaa');
}
let [x = f()] = [1];//函数f()不会执行
解构不仅可以用于数组,还可以用于对象。但是对象的解构与数组的结构有一个重要的不同。数组的元素是按次序排列的,变量的取值是由它的位置决定的;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。
var {bar,foo} = {foo:'aaa',bar:'bbb'};//bar的值为‘bbb’ foo的值为'aaa'
var {baz} = {foo:'aaa',bar:'bbb'};//baz变量没有对应的同名属性 导致其值为undefined
对象的解构赋值的内部机制是先找到同名属性,然后在赋给对应的变量。真正被赋值的是后者,而不是前者。
var {foo:baz} = {foo:'aaa',bar:'bbb'};
console.log(baz);//'aaa'
console.log(foo);//error:foo is not defined
注意:采用解构赋值的写法时,变量的声明和赋值是一体的。对于let和const而言,变量不能重新声明,所以一旦赋值的变量以前声明过,就会报错。不过var命令允许重新声明,所以这个错误只会在使用let和const命令时出现。
let foo;
let {foo} = {foo:1};//SyntaxError: redeclaration of let foo
let baz;
let {bar:baz} = {bar :1};//SyntaxError: redeclaration of let baz
与数组解构赋值相同,对象的解构也可以指定默认值。默认值的生效条件是:对象的属性值严格等于undefined。
var {x = 3} = {}; //x = 3
var {x = 3} = {x : undefined};//x = 3
var {x = 3} = {x:null};//x = null
对象的解构赋值可以使用嵌套对象赋值。
var {foo:{bar}} = {foo:{bar:'aaa'}};//bar的值为‘aaa’
如果将一个已经声明的变量用于解构赋值,JavaScript引擎会将{}理解成一个代码块,从而发生语法错误。可通过外加小括号避免将其解释为代码块。
{x} = {x:1};//SyntaxError: expected expression, got '='
//正确写法
({x} = {x:1})//x的值为1
对象的解构赋值可以很方便地将现有对象的方法赋值到某个变量。
//将Math对象的取对数、正弦、余弦三个方法赋值到对应变量
let {log,sin,cos} = Math;
console.log(sin(1));