在es5时,只有两种变量声明,var 和function。在es6中新增了四种let和const,以及另外两种声明import和class。 咱们先讲解let和const,后续会补充import和classjava
咱们先来看基本语法es6
{
let a = 10;
var b = 1;
}
b // 1
a // ReferenceError: a is not defined.
复制代码
咱们在代码块中声明了a,b。而后a is not defined.这是由于let命令只在对应的代码块中有效,咱们在外部去引用它,就会报错。这就是let的块级做用域的效果,若是不太清楚什么是块级做用域。咱们来看下面的例子编程
var a = [];
for (var i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[0]();//10
a[6](); // 10
复制代码
这是一个老生常谈的问题了。i在for循环中定义的是全局变量。咱们在调用函数时。函数内部的i引用的是全局的i,因此打印出来的 是10. 咱们以前是这样解决的。数组
var a = [];
for (var i = 0; i < 10; i++) {
a[i] = (function (a) {
return function(){
console.log(a);
}
}(i));
}
a[0]();//0
a[6](); // 6
复制代码
咱们使用了当即执行函数将i的值传入了内部的函数,这样内部的函数就可以获取到对应的i。bash
咱们用let来代替var,最后输出的是 6。app
var a = [];
for (let i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 6
复制代码
这是由于咱们每一次的循环都生成了一个新的块级做用域,内部保存着i的值,因此就会打印出6.async
let不存在变量提高函数
console.log(a);
console.log(b);
var a=0;//undefined
let b=0;//ReferenceError: b is not defined
复制代码
TDZ(暂时性死区) let命令在块级做用域中,即便不存在变量提高,它也会影响当前块级做用域,即绑定在了当前做用域。在做用域中引用外部的变量将会报错。优化
var a=10;
{
console.log(a); //ReferenceError: a is not defined
let a=10;
}
复制代码
同时,咱们在TDZ中使用typeof也会报错.ui
console.log( typeof a);//undefined
复制代码
console.log( typeof a);//ReferenceError: a is not defined
let a=10;
复制代码
let 不容许重复声明变量
{
var a=0;
let a=1;//SyntaxError: Identifier 'a' has already been declared
}
复制代码
const常量声明一个只读属性的变量,不可更改,不可先声明后赋值,生成块级做用域。
const a;//SyntaxError: Missing initializer in const declaration(const声明中缺乏初始值设定项)
a=10;
复制代码
它同let有不少类似的地方。 .不可重复声明
const a=10;
var a=10;//SyntaxError: Identifier 'a' has already been declared
复制代码
.变量不提高
console.log(a)//ReferenceError: a is not defined
const a=10;
复制代码
.一样存在暂时性死区
var a=10;
{
console.log(a);//ReferenceError: a is not defined
const a=10;
}
复制代码
另外,const保证常量的值不被修改的原理是什么呢?const实际上没法改变的只是常量在栈区的值不变,若是这个值是一个基本数据类型,那么const可以保障常量的值不变,但若是是引用类型的数据,栈区保存的实际上是对应常量的地址。地址没法改变,可是对应地址的堆区内容却能够改变。
const a=[1,2,3]
a.push(4)
console.log(a); //[1, 2, 3, 4]
复制代码
很显然,咱们经过push,直接修改了堆区的内容,间接改变了const常量的值。
若是要真正作到常量的功能,可使用Object.freeze()
var a=[1,2,3];
Object.freeze(a)
a.push(4) //Cannot add property 3, object is not extensibleat Array.push
console.log(a);
复制代码
使用for of能够遍历字符串,并将字符串分解为单独的字符串
var a='lang'
for (item of a){
console.log(item);
}
// l
// a
// n
// g
复制代码
根据下标,查询对应字符串。在Es5时,就有一个charAt()方法。但charAt()显然没想到,随着Unicode编码的扩展,原先的0x0000~0xFFFF已经没法知足表示全部字符了。
var text = "𠮷";
text.charAt(0); //''
text.charAt(1); //''
复制代码
因此es6中新增了codePointAt(),查询,codePointAt()有着更好的unicode支持,可以查询>0xFFFF的字符。
var text = "𠮷";
console.log(text.codePointAt(0)); //134071
复制代码
在es6以前使用indexof也能够进行值存在判断。includes与indexof既能够进行字符串的判断,也能够进行数组值的判断, 可是indexof在对NaN进行判断时会出现不许确。
var text = [1,NaN]
console.log(text.indexOf(NaN));//-1
复制代码
另外,indexof的返回结果为-1||0,includes为true||false.
str=repeat(n)返回的是新的字符串,而且会将str的字符串重复n次。
var a='lang'
console.log(a.repeat(3));//langlanglang
复制代码
其中n会自动取整。n<=-1||n==infinaty将会报错。
startWith('str',n):返回布尔值,表示参数字符串是否在原字符串的头部。
endsWith('str',n):返回布尔值,表示参数字符串是否在原字符串的尾部。其中str表示要判断的值,n表示从目标字符串的第几个元素开始。
var str='hello world'
console.log(str.startsWith('hello',0)); //true
console.log(str.startsWith('world',6)); //true
console.log(str.startsWith('world',0)); //false
复制代码
es6提供了两个字符串追加的方法String.prototype.padStart和String.prototype.padEnd,方便咱们将一个新的字符串追加到某个字符串的头尾。
咱们经常使用padstart来使字符串输出时保持格式。
var a='abc'
var b='abcd'
var c='abcd'
console.log(a.padStart('10',0));
console.log(b.padStart('10',0));
console.log(c.padStart('10',0));
//0000000abc
//000000abcd
//00000abcde
复制代码
但有时候使用endstart显然会更好。
var a='abc'
var b='abcd'
var c='abcde'
console.log(a.padEnd(10,'-------'));
console.log(b.padEnd(10,'-------'));
console.log(c.padEnd(10,'-------'));
//abc-------
//abcd------
//abcde-----
复制代码
也能够组合使用达到效果
var obj={
name:'wangcai',
car:'BMW',
wife:'fatgirl'
}
for(var item in obj){
console.log(item.padEnd(10,'-')+'value:'+obj[item].padStart(10,'**'));
}
//name------value:***wangcai
//car-------value:*******BMW
//wife------value:***fatgirl
复制代码
模板字符串的引入是es6的一大亮点,它使得输出模板变得简洁而方便。模板采用反引号(``),中间支持各类格式的输出。 包括换行,空格,变量,表达式,调用函数等。咱们能够在一个模板中组合使用它们
var age=22;
var name='lang'
var say=()=>{
return 'hello'
}
var str=`myname is${name} my age is ${age} and i can say ${say()}`
console.log(str); //myname islang my age is 22 and i can say hello
复制代码
在模板字符串的 ${} 中能够写任意表达式,可是一样的,对 if / else 判断、循环语句没法处理。
但在不少时候咱们须要去使用if或者循环。咱们能够先在外部使用逻辑处理语句,而后生成一个咱们想要的模板,在用``进行转义
var age=22;
var name='lang'
var name2='Lang'
var str=''
var say=()=>{
return 'hello'
}
if(age>=18){str=name2}
var str=`myname is${str} my age is ${age} and i can say ${say}`
console.log(str); //myname isLang my age is 22 and i can say hello
复制代码
固然,咱们也可使用数组,将各个模板片断存入数组之中,最后经过Array.join('')将其拼接为最终的模板。
以前在es5时的严格模式中,,咱们已经没法使用二进制和八进制。在es6中提供了两个新的二进制和八进制写法。
二进制(0bxxx||0Bxxx)八进制(0oxxx||0Oxxx)
'use strict'
var a=0b11;
var b=0o11
console.log(a);//3
console.log(b);//9
复制代码
isNAN能够用来检测数据是不是NAN类型,只有NAN才会返回true,其他类型皆返回false。
var x=NaN*2
console.log(Number.isNaN(x));//true
console.log(NaN==NaN);//false
console.log(NaN===NaN);//false
复制代码
这两个方法在es5中已经存在,es6将其从全局对象中提取放入了Number对象中
var a=3.00
var b=10;
var c=false;
var d=4.00000000000000000000000000000002
console.log(Number.isInteger(a));//true
console.log(Number.isInteger(b));//true
console.log(Number.isInteger(c));//false
console.log(Number.isInteger(d));//true
复制代码
只有整数,以及相似3.0这样的浮点数才会被认为是整数,返回true,除此以外,js运算具备不许确性,超出精度范围的值,会默认为o,因此4.0000000000000000000000002会被看作是4. (5)Math.trunc()
取整函数,会对值进行取整,会对传入的值先进行Number()处理,正数将会进行Math.floor(),若为负数则进行Math.ceil();
console.log(Math.trunc(4.1));//4
console.log(Math.trunc(4.9));//4
console.log(Math.trunc(-1.2));//-1
console.log(Math.trunc(-1.9));//-1
console.log(Math.trunc(true));//1
console.log(Math.trunc('lang'));//NaN
复制代码
(1)函数指定默认值 能够为函数传入的参数指定默认值,函数内部能够覆盖使用。
function say(x,y=5){
console.log(x,y); //1,5
y=10;
console.log(x,y); //1,10
}
say(1)
复制代码
须要注意如下两点
.使用参数默认值时,函数不能有同名参数。
// 不报错
function foo(x, x, y) {
// ...
}
// 报错
function foo(x, x, y = 1) {
// ...
}
// SyntaxError: Duplicate parameter name not allowed in this context
复制代码
.不可以使用let,const重复声明
function say(x,y=5){
let y=10;
console.log(x,y); //SyntaxError: Identifier 'y' has already been declared
}
say(1)
复制代码
(2)rest 参数 在函数形参中使用...扩展运算符,能够将不定形参传入rest数组中。
function say(...res) {
for (var item of res) {
console.log(item);
}
}
say(1, 2, 3)
//1
//2
//3
复制代码
基本使用方法
var f=(x,y)=>{ return x+y}
console.log(f(1,2)); //3
复制代码
假如(x,y)只有一个参数,咱们能够省略(),一样返回语句中,若只有一条语句,也能够省略。
var f=x=>x+10
console.log(f(1)); //11
复制代码
使用注意点 箭头函数有几个使用注意点。
(1)函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
(2)不能够看成构造函数,也就是说,不可使用new命令,不然会抛出一个错误。
(3)不可使用arguments对象,该对象在函数体内不存在。若是要用,能够用 rest 参数代替。
(4)不可使用yield命令,所以箭头函数不能用做 Generator 函数。
对于须要注意的第一点,咱们常常会由于维护回调函数的this而烦恼,而在箭头函数中则不存在这个问题,箭头函数内部的this是固定的,不会由于函数调用而改变。
在ES5,咱们一般采用外部保存this的方法,来维护this、
// ES6
function foo() {
setTimeout(() => {
console.log('id:', this.id);
}, 100);
}
// ES5
function foo() {
var that= this;
setTimeout(function () {
console.log('id:', that.id);
}, 100);
}
复制代码
argumet在箭头函数中不存在,那么使用argument将会使用外部函数的argument
function foo() {
setTimeout(() => {
console.log('args:', arguments);
}, 100);
}
foo(2, 4, 6, 8)
// args: [2, 4, 6, 8]
复制代码
在函数的最后一步调用一个函数,这叫作尾调用。解释很简单,但却很容易混淆。 .最后一步?什么是最后一步,在函数中,return就是最后一步,没有return的都不是尾调用。
function g(){}
function f(){
g() //这不是尾调用
}
f()
复制代码
.即便return后面还有表达式,但这些函数不起做用,那么它依旧是尾调用。
function g(){}
function f(){
return g() //这是尾调用
console.log('121');
}
f()
复制代码
.返回的必须是函数,不能是表达式。下面额例子中,返回了g()+1; g()+1;不是一个函数,而是一个表达式。
function g(){}
function f(){
return g()+1 //这不是尾调用
}
f()
复制代码
尾递归是尾调用的一种特殊状况,但尾递归是在最后一步调用自身。
咱们在使用递归时,必须给一个终止条件,否则就会产生死循环
var a=0;
function f(){
a++
return f() //这显然是一个死循环
}
f()
复制代码
咱们经常使用递归等方法来求阶乘等,但递归很容易发生栈溢出的状况。
非尾递归的 Fibonacci 数列实现以下。
function Fibonacci (n) {
if ( n <= 1 ) {return 1};
return Fibonacci(n - 1) + Fibonacci(n - 2);
}
Fibonacci(10) // 89
Fibonacci(100) // 堆栈溢出
Fibonacci(500) // 堆栈溢出
复制代码
尾递归优化过的 Fibonacci 数列实现以下。
function Fibonacci2 (n , ac1 = 1 , ac2 = 1) {
if( n <= 1 ) {return ac2};
return Fibonacci2 (n - 1, ac2, ac1 + ac2);
}
Fibonacci2(100) // 573147844013817200000
Fibonacci2(1000) // 7.0330367711422765e+208
Fibonacci2(10000) // Infinity
复制代码
使用扩展运算符能够 可以直接深拷贝一个数组。修改一个数组内的引用值,不会改变另外一个值
var arr=[1,2,3,[4,5],6]
var a=[...arr];
console.log(a);//[1, 2, 3, Array(2), 6]
复制代码
扩展运算符能够用于数组拼接
var arr=[1,2,3]
var arr2=[4,5,6]
var str='12121'
var a=[1,...arr,2,...arr2];
复制代码
另外...arr返回的并非一个数组,而是各个数组的值。只有[...arr]才是一个数组,因此...arr。能够用来对方法进行传值
var arr=[1,2,3]
function f(x,y,z){
return x+y+z
}
console.log(f(...arr)); //6
复制代码
Array.from()能够将某些伪数组转换成真正的数组结果,什么是伪数组呢,咱们在实际开发中,有两种常见的伪数组,arguments和Dom中获取的元素集合。
<body>
<p></p>
<p></p>
<p></p>
<p></p>
<p></p>
</body>
<script>
var arr = document.getElementsByTagName('p')
var arr2 = Array.from(arr)
console.log(Array.isArray(arr2));//true
复制代码
一样可以将伪数组转换数组的还有两种方法 .以前提到的扩展运算符
var arr = document.getElementsByTagName('p')
var arr2 = [...arr]
console.log(Array.isArray(arr2));//true
复制代码
.使用call,apply方法。
var arr = document.getElementsByTagName('p')
var newarr=Array.prototype.slice.call(arr,0)
var newarr=Array.prototype.slice.apply(arr,[0])
console.log(Array.isArray(newarr));//Var 新数组 = 旧数组.slice(起点下标,终点下标)返回值:数组,是旧数组中的一个部分。
console.log(newarr);
复制代码
也是用于将一组值,转换为数组。 Array.of并非用于将转换伪数组的,它的做用是为了弥补Array构造器的不足,以前咱们在想要构建一个长度为一的数组,且值为number类型是没法用Array构建的
var arr=new Array(3)
console.log(arr);//[empty*3]
复制代码
很显然,咱们想要构建一个[3],可使用Array.of()
var arr=Array.of(3)
console.log(arr); //[3]
复制代码
find:用于找出第一个符合条件的数组元素。找不返回 undefined 。
findIndex:返回第一个符合条件的数组元素的索引。找不到返回-1;
var arr=[1,2,3,4,5]
var newarr1=arr.find(function(item,index){return item>2})
var newarr2=arr.findIndex(function(item,index){return item>2})
console.log(newarr1); //3
console.log(newarr2); //2
复制代码
基本语法如上:find和findindex内部是一个回调函数,须要返回一个查询条件,find则会执行这个返回条件,查询第一个知足条件的值。findindex则会返回下标。 咱们能够直接用箭头函数进行简写
var arr=[1,2,3,4,5]
var newarr1=arr.find(item=>item>2)
var newarr2=arr.findIndex(item=>item>2)
console.log(newarr1); //3
console.log(newarr2); //2
复制代码
做用:给数组填充指定值。fill 方法用于空数组的初始化很是方便。已有数据会被覆盖。 fill 方法还能够接受第二个和第三个参数,用于指定填充的起始位置和结束位置
var arr=[1,2,3,4,5]
arr.fill('*',1,3)
console.log(arr);//[1, "*", "*", 4, 5]
复制代码
属性名是能够简写的,可是有前提条件:属性的值是一个变量 变量名称和键名是一致的。
var name ='lang'
var age=22;
var obj={
name:name,
age:age
}
复制代码
像这样的对象,咱们就能够进行简写
var name ='lang'
var age=22;
var obj={
name,
age,
}
复制代码
前提是属性名和属性值必须一致。
var obj={
say:function(){}
}
复制代码
简写为
var obj={
say(){}
}
复制代码
属性的四个特征:
1.configurable: 是否能够删除。 默认为true 能够删除:
2.writable: 是否能够修改。 默认为ture, 能够修改:
3.enumerable: 是否能够枚举。可使用 for in 默认为ture, 能够枚举:
4.value: 值。 默认值为undefined
格式1:Object.defineProperty(对象名,“属性名”,{上面的四个特征});
var obj={
name:'lang',
age:22
}
Object.defineProperty('obj','name',{
configurable:false,
writable:false,
enumerable:false
})
复制代码
格式2:Object.defineProperties(对象名,{属性名{四个特征}},{属性名{四个特征}}});
var obj = {
name: 'lang',
age: 22
}
Object.defineProperties(obj, {
name: {
configurable: false,
writable: false,
enumerable: false
},
age: {
configurable: false,
writable: false,
enumerable: false
}
})
复制代码
Obect.getOwnProertyDescriptor(对象,属性名) 能够获取对象的精细化属性里面的值。
var obj={
name:'lang',
age:22
}
Object.defineProperty(obj,'name',{
configurable:false,
writable:false,
enumerable:false
})
console.log(Object.getOwnPropertyDescriptor(obj,'name'));
复制代码
使用Object.getOwnPropertyNames()和Object.keys()均可以获得一个对象的属性名,属性名是放在一个数组中的。
var obj={
name:'lang',
age:22
}
console.log(Object.keys(obj)); //["name", "age"]
复制代码
那么咱们目前有三种遍历对象的方法了 对于对象的遍历目前有三种方式:
1. for in
2.Object.keys()
3.Object.getOwnPropertyNames()
复制代码
for in : 会输出自身以及原型链上可枚举的属性。
Object.keys() : 用来获取对象自身可枚举的属性键
Object.getOwnPropertyNames() : 用来获取对象自身的所有属性名
获取对象的值,放入数组中。
var obj={
name:'lang',
age:22
}
console.log(Object.values(obj)); //["lang", 22]
复制代码
解构赋值语法是一个 Javascript 表达式,这使得能够将值从数组或属性从对象提取到不一样的变量中。
对象字面量和数组字面量提供了一种简单的定义一个特定的数据组的方法。
let x = [1, 2, 3, 4, 5];
复制代码
解构赋值使用了相同的语法,不一样的是在表达式左边定义了要从原变量中取出什么变量。
var x = [1, 2, 3, 4, 5];
var [y, z] = x;
console.log(y); // 1
console.log(z); // 2
复制代码
变量声明并赋值时的解构
var foo = ["one", "two", "three"];
var [one, two, three] = foo;
console.log(one); // "one"
console.log(two); // "two"
console.log(three); // "three"
Link to section变量先声明后赋值时的解构
复制代码
经过解构分离变量的声明,能够为一个变量赋值。
var a, b;
[a, b] = [1, 2];
console.log(a); // 1
console.log(b); // 2
Link to section默认值
复制代码
为了防止从数组中取出一个值为undefined的对象,能够为这个对象设置默认值。
var a, b;
[a=5, b=7] = [1];
console.log(a); // 1
console.log(b); // 7
Link to section交换变量
复制代码
在一个解构表达式中能够交换两个变量的值。
没有解构赋值的状况下,交换两个变量须要一个临时变量(或者用低级语言中的XOR-swap技巧)。
var a = 1;
var b = 3;
[a, b] = [b, a];
console.log(a); // 3
console.log(b); // 1
Link to section解析一个从函数返回的数组
复制代码
从一个函数返回一个数组是十分常见的状况.。解构使得处理返回值为数组时更加方便。
在下面例子中,[1, 2] 做为函数的 f() 的输出值,可使用解构用一句话完成解析。
function f() {
return [1, 2];
}
var a, b;
[a, b] = f();
console.log(a); // 1
console.log(b); // 2
复制代码
基本赋值
var o = {p: 42, q: true};
var {p, q} = o;
console.log(p); // 42
console.log(q); // true
复制代码
给新的变量名赋值
能够从一个对象中提取变量并赋值给和对象属性名不一样的新的变量名。
var o = {p: 42, q: true};
var {p: foo, q: bar} = o;
console.log(foo); // 42
console.log(bar); // true
复制代码
默认值
变量能够先赋予默认值。当要提取的对象没有对应的属性,变量就被赋予默认值。
var {a = 10, b = 5} = {a: 3};
console.log(a); // 3
console.log(b); // 5
复制代码
混合解构(嵌套对象和数组) 解构嵌套对象和数组
var metadata = {
title: "Scratchpad",
translations: [
{
locale: "de",
localization_tags: [ ],
last_edit: "2014-04-14T08:43:37",
url: "/de/docs/Tools/Scratchpad",
title: "JavaScript-Umgebung"
}
],
url: "/en-US/docs/Tools/Scratchpad"
};
var { title: englishTitle, translations: [{ title: localeTitle }] } = metadata;
console.log(englishTitle); // "Scratchpad"
console.log(localeTitle); // "JavaScript-Umgebung"
复制代码
以前在es5中,咱们如何去实现一个类的功能呢?咱们一般采用构造器的方法去实现,然而,使用构造器去模仿类的实现并不方便,不只须要常常维护this,并且在继承的时候更是不只须要使用call拷贝父类的基本数值,还须要继承父类的原型来继承方法。咱们简单来看看代码就知道了
function Parent(name) {
this.name = name;
}
Parent.prototype.sayName = function () {
console.log('parent name:', this.name);
}
function Child(name, parentName) {
Parent.call(this, parentName);
this.name = name;
}
function inheritPrototype(Parent, Child) {
Child.prototype = Object.create(Parent.prototype); //修改
Child.prototype.constructor = Child;
}
inheritPrototype(Parent, Child);
Child.prototype.sayName = function () {
console.log('child name:', this.name);
}
var parent = new Parent('father');
parent.sayName(); // parent name: father
var child = new Child('son', 'father');
child.sayName(); // child name: son
复制代码
在es6中,咱们能够直接使用class来定义,若是你有Java基础的话,那么理解起来将很是简单。 基本上,ES6 的class能够看做只是一个语法糖,它的绝大部分功能,ES5 均可以作到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。
//定义类
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}
复制代码
咱们使用typeof去检测class的数据类型,会发现class类的本质就是一个方法,也就是构造器。咱们不只仅可使用new方法来新建一个类,咱们也可使用prototype来访问类的原型。
能够看到里面有一个constructor方法,这就是构造方法,而this关键字则表明实例对象。这里面一般保存着类基本数据类型
咱们能够直接在类中添加本身的方法,前面不须要加上function这个关键字,另外,为了使类更加的符合大众的写法,去掉了逗号分隔,咱们不须要在方法之间使用逗号进行分隔。
es6还给类提供了一个extends的继承方法。使用方法与java一模一样。
class NBAPlayer2 {
constructor(name, height) {
this.name = name;
this.height = height;
}
say() {
console.log(`name is${this.name} height is${this.height}`);
}
}
class MVP2 extends NBAPlayer {
constructor(name, height, year) {
super(name, height)
this.year = year
}
say() {
console.log(`name is${this.name} height is${this.height} mvpyear is${this.year}`);
}
}
var n1 = new MVP2('老库里', '201', '2016')
var m1 = new NBAPlayer2('老库里', '201')
n1.say()
m1.say()
复制代码
注意:使用 extends 关键字来实现继承在子类中的构造器 constructor 中,必需要显式调用父类的 super 方法,若是不调用,则 this 不可用
咱们若是使用原型去查看父子类,就会发现,他们实际上是经过原型链来进行继承的。
ES6的新特性还有不少,本次只概述了一些较为经常使用的方法。适合初识es6的人对es6进行一个大概的了解。
关于es6的更多特性,将会在后续进行补充。具体重要内容还有如下
1.Promise 对象
2.Generator
3.async 函数
4.Module 语法
具体深刻了解能够参考阮大大的 ECMAScript 6 入门