因为js的历史缘由。javascript里的es6规范以前,只要函数做用域。当写一些大型的项目代码中,很容易产生全局变量,例如:javascript
<body> <button>ok</button> <button>ok</button> <button>ok</button> <button>ok</button> <script> var btn=document.querySelectorAll("button"); for(var i=0;i<btn.length;i++){ btn[i].onclick=function(){ alert(i) } } </script> </body>
点击每一个按钮,都结果都是4;java
<body> <button>ok</button> <button>ok</button> <button>ok</button> <button>ok</button> <script> var btn=document.querySelectorAll("button"); for(let i=0;i<btn.length;i++){ btn[i].onclick=function(){ alert(i) } } </script> </body>
0,1,2,3
当使用let定义变量的时候。点击每一个按钮,都结果显示的都是对应的i值。git
如今咱们可使用let 定义块级变量。 只在代码块{}内生效;
如:es6
<script> { var a=1 } alert(a) </script>
弹1数组
<script> { let a=1 } alert(a) </script>
报错 app
使用块级应该注意:
1.块级做用域函数
for(var i = 0; i < 10; i++){} console.log(i); //10 for(let j = 0; j < 10; j++){} console.log(j); //"ReferenceError: j is not defined
2.不存在变量提高
会报错,ReferenceErrorthis
console.log(a); // 输出undefined console.log(b); // 报错ReferenceError console.log(c); // 报错ReferenceError var a = 2; let b = 2;
3.暂时性死区(TDZ)
只要进入当前块级做用域,所使用的变量已经存在了,但在声明以前都属于死区,不可进行操做。es5
typeof x; // ReferenceError typeof y // undefined let x;
4.不容许重复声明spa
let x = 1; let x; // "SyntaxError: Identifier 'x' has already been declared var y = 2; var y = 3; // y = 3
5.声明的全局变量再也不是window的属性
"use strict"; var a = 1; console.log(window.a) // 1 let b = 1; console.log(window.b) // undefined
const声明一个只读的常量。一旦声明,常量的值就不能改变
const PI = 3.1415; PI // 3.1415 PI = 3; // TypeError: Assignment to constant variable.
上面代码代表改变常量的值会报错。
const声明的变量不得改变值,这意味着,const一旦声明变量,就必须当即初始化,不能留到之后赋值。
const foo; // SyntaxError: Missing initializer in const declaration 使用const定义常量应该注意:
1,const的做用域与let命令相同:只在声明所在的块级做用域内有效。
2,const命令声明的常量也是不提高,一样存在暂时性死区,只能在声明的位置后面使用。
3,const声明的常量,也与let同样不可重复声明。
const本质:
const实际上保证的并非变量值不得改动,而是变量指向的那个内存地址不得改变。对于简单类型的数据(数值,字符串,布尔值),值就是指向的那个内存地址,所以等同于常量。可是对于复合类型的数据(注意是对象和数组),变量指向的是内存地址,保存的只是一个指针,const只能保证这个指针是固定的,至于他指向的数据机构是否是可变的,就不能控制了,所以,将一个对象声明为常量是很是当心的。
const foo = {}; // 为 foo 添加一个属性,能够成功 foo.prop = 123; foo.prop // 123 // 将 foo 指向另外一个对象,就会报错 foo = {}; // TypeError: "foo" is read-only
上面代码中,常量foo储存的是一个地址,这个地址指向一个对象。不可变的只是这个地址,即不能把foo指向另外一个地址,但对象自己是可变的,因此依然能够为其添加新属性。
下面是另外一个例子。
const a = []; a.push('Hello'); // 可执行 a.length = 0; // 可执行 a = ['Dave']; // 报错 上面代码中,常量a是一个数组,这个数组自己是可写的,可是若是将另外一个数组赋值给a,就会报错。
ES6 声明变量的六种方法 ES5 只有两种声明变量的方法:var命令和function命令。ES6 除了添加let和const命令,后面章节还会提到,另外两种声明变量的方法:import命令和class命令。因此,ES6 一共有 6 种声明变量的方法。
rest 参数
ES6 引入 rest 参数(形式为...变量名),用于获取函数的多余参数,这样就不须要使用arguments对象了。rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。
<script>
function add(...values) { let sum = 0; for (var val of values) { sum += val; } return sum; } console.log(add(2, 5, 3)) // 10
</script>
注意在es6中rest参数只能够是形参的最后一位参数。否则会报错
<script>
function add(...values,a) { let sum = 0; for (var val of values) { sum += val; } return sum; } console.log(add(2, 5, 3))
</script>
以前咱们定义一个函数是这样的:
let add=function () { alert(123) } add()
如今咱们能够改为箭头函数
let ale=()=>{alert(234)}
ale()
改为箭头函数无非就是把代码简写了,应该注意一下三点:
1,把普通函数的function换成=>.
2,若是只有一个参数,那么()可省略不写。
2,若是只有一个return 那么{}可省略不写。
例如:
<script>
// 普通函数:
let add=function (a) { return a*2 } console.log(add('hellow'))
// 改为es6箭头函数
let add= a => a*2 console.log(add('3'))
</script>
1,数组的解构赋值
基本用法
ES6 容许按照必定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构。
之前,为变量赋值,只能直接指定值。
let a=1 let b=2 let c=3
ES6 容许写成下面这样。
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 // []
若是解构不成功,变量的值就等于undefined。如下两种状况都属于解构不成功,foo的值都会等于undefined。
let [foo] = []; let [bar, foo] = [1];
es6中数组有经常使用的四个方法依次是:
map ----映射 一个对一个
reduce -----汇总 一堆出来一个
filter -----过滤器
forEach -----循环(迭代)
1.map用法
<script>
let arr=[45 ,53,96,42,86]
let arr2=[]
arr2=arr.map(function(item){
if(item>=60){ return "及格" }else{ return "不及格" }
})
console.log(arr2)
</script>
也可改为箭头函数
<script> let arr=[84 ,53,96,42,86] let arr2=[] arr2=arr.map(item=>item>=60?"及格":"不及格") console.log(arr2) </script>
循环数组,每次都乘2倍,并返回新数组
<script> let arr=[84 ,53,96,42,86] let arr2=[] arr2=arr.map(item=>item*2) console.log(arr2) </script>
2.reduce
let arr=[2,14,31,1,4,3] arr.reduce(function(a,b,c){ alert(a+","+b+","+c) })
先看弹什么结果,依次是
先看结果。明显 c参数是下标index,可是有不是0开始。
b是当前的数。 其实a就是最终结果,把上一次算好的数当成了此次的中间结果。 let arr=[2,14,31,1,4,3] let result=arr.reduce(function(tmp,item,index){ return tmp+item }) alert(result)
这就是求和的方式
reduce 流程图
求平均数
let arr=[2,14,31,1,4,3] let result=arr.reduce(function(tmp,item,index){ if(index !=arr.length-1){ //不是最后一项 return tmp+item }else{ //最后一项 return (tmp+item)/arr.length } }) alert(result)
3.filter 过滤器 留下符合条件的一部分;
经过filter里面的function里返回的true和false判断留或者不留。
let arr=[2,14,31,1,4,3] let arr2=arr.filter(()=>{ return true }) alert(arr2) //[2,14,31,1,4,3]
由于返回的true因此都留下了
let arr=[2,14,31,1,4,3] let arr2=arr.filter(()=>{ return false }) alert(arr2) // [ ]
由于返回的false因此都不留下
下面的取能够被3整除的数
let arr=[2,14,33,1,4,3,66] let arr2=arr.filter((item)=>{ if(item%3==0){ return true }else{ return false } }) alert(arr2) //33,3,66
因为item%3==0自己就是布尔值,因此直接能够return这个,即就能够返回符合条件的数。
因此能够写成,
let arr=[2,14,33,1,4,3,66] let arr2=arr.filter((item)=>{ return item%3==0 }) alert(arr2) //33,3,66
还能够简写成箭头函数
let arr=[2,14,33,1,4,3,66] let arr2=arr.filter(item=>item%3==0) alert(arr2) //33,3,66
4.forEach 就是普通的循环,没法就是写法简单一些,
let arr=[2,14,33,1,4,3,66] arr.forEach(item=>{ alert(item) })
它第二个参数是可选的,是index
let arr=[2,14,33] arr.forEach((item,index)=>{ alert(index+":"+item) }) //0:2 //1:14 //2:33
(一):
1.includes():返回布尔值,表示是否找到了参数字符串。
2.startsWith():返回布尔值,表示参数字符串是否在原字符串的头部。
3.endsWith():返回布尔值,表示参数字符串是否在原字符串的尾部。
includes()用法
let s = 'Hello world!'; s.startsWith('Hello') // true s.endsWith('!') // true s.includes('o') // true
startsWith()用法
let str="git.baidu.com" switch(true) { case str.startsWith('www.'): console.log('www网址') break; case str.startsWith('git.'): console.log('git网址') break; default: console.log('其余网址') }
endsWith()用法
let str="1.jpg" switch(true) { case str.endsWith('txt'): console.log('txt文件') break; case str.endsWith('jpg'): console.log('jpg文件') break; default: console.log('其余网址') }
(二):模板字符串 (返单引号`)
$('#result').append( 'There are <b>' + basket.count + '</b> ' + 'items in your basket, ' + '<em>' + basket.onSale + '</em> are on sale!' );
大括号内部能够放入任意的 JavaScript 表达式,能够进行运算,以及引用对象属性。
let x = 1; let y = 2; `${x} + ${y} = ${x + y}` // "1 + 2 = 3" `${x} + ${y * 2} = ${x + y * 2}` // "1 + 4 = 5" let obj = {x: 1, y: 2}; `${obj.x + obj.y}` // "3"
模板字符串之中还能调用函数。
function fn() { return "Hello World"; } `foo ${fn()} bar` // foo Hello World bar
JavaScript 语言中,生成实例对象的传统方法是经过构造函数。下面是一个例子。
function Point(x, y) { this.x = x; this.y = y; console.log(this,12) } Point.prototype.toString = function () { return '(' + this.x + ', ' + this.y + ')'; }; var p = new Point(1, 2); console.log(typeof p.toString())
其很差的地方就是,构造函数和类混为一谈了,别的语言(java)中构造函数就是构造函数,类就是类。
并且代码写了两块,方法经过prototype添加。还有就是这个是es6以前的方式生成对象。es6以前的js里中是没有类的概念的,这些方式都是东拼西凑的。
写到这里,能够暂时回顾一下es6以前的创造对象的时候 new 关键字都作了什么。
要建立 Person 的新实例,必须使用 new 操做符。以这种方式调用构造函数实际上会经历如下 4
个步骤:
(1) 建立一个新对象;
(2) 将构造函数的做用域赋给新对象(所以 this 就指向了这个新对象) ;
(3) 执行构造函数中的代码(为这个新对象添加属性) ;
(4) 返回新对象。
new 操做符
在有上面的基础概念的介绍以后,在加上new操做符,咱们就能完成传统面向对象的class + new的方式建立对象,在JavaScript中,咱们将这类方式成为Pseudoclassical。
基于上面的例子,咱们执行以下代码
var obj = new Base();
new操做符具体干了什么呢?其实很简单,就干了三件事情。
var obj = {}; obj.__proto__ = Base.prototype; Base.call(obj);
第一行,咱们建立了一个空对象obj
第二行,咱们将这个空对象的__proto__成员指向了Base函数对象prototype成员对象
第三行,咱们将Base函数对象的this指针替换成obj,而后再调用Base函数,因而咱们就给obj对象赋值了一个id成员变量,这个成员变量的值是”base”,关于call函数的用法。
若是咱们给Base.prototype的对象添加一些函数会有什么效果呢?
例如代码以下:
Base.prototype.toString = function() { return this.id; }
那么当咱们使用new建立一个新对象的时候,根据__proto__的特性,toString这个方法也能够作新对象的方法被访问到。因而咱们看到了构造子中,咱们来设置‘类’的成员变量(例如:例子中的id),构造子对象prototype中咱们来设置‘类’的公共方法。因而经过函数对象和Javascript特有的__proto__与prototype成员及new操做符,模拟出类和类实例化的效果。
如今的es6中创做对象的方式就及其简单。
class Point { constructor (x,y){ this.x=x; this.y=y } tostring (){ return '(' + this.x + ', ' + this.y + ')'; } } let a=new Point(2,3) alert(typeof a.tostring()) //string
上面代码定义了一个“类”,能够看到里面有一个constructor方法,这就是构造方法,并且this关键字则表明实例对象,也就是说,es5的构造函数Point,对应es6的point类的构造方法。
point类除了构造方法,还定义了一个tostring方法。注意,定义’类‘的方法时候,前面不须要加上function这个关键字,直接把函数定义进去级能够了。类外,方法之间不须要逗号分隔,加上会报错。
ES6 的类,彻底能够看做构造函数的另外一种写法。
class Point { // ... } typeof Point // "function" Point === Point.prototype.constructor // true
上面代码代表,类的数据类型就是函数,类自己就指向构造函数。
使用的时候,也是直接对类使用new命令,跟构造函数的用法彻底一致。
class Bar { doStuff() { console.log('stuff'); } } var b = new Bar(); b.doStuff() // "stuff"
构造函数的prototype属性,在 ES6 的“类”上面继续存在。事实上,类的全部方法都定义在类的prototype属性上面。
class Point { constructor() { // ... } toString() { // ... } toValue() { // ... } } // 等同于 Point.prototype = { constructor() {}, toString() {}, toValue() {}, };
在类的实例上面调用方法,其实就是调用原型上的方法。
class B {} let b = new B(); b.constructor === B.prototype.constructor // true
上面代码中,b是B类的实例,它的constructor方法就是B类原型的constructor方法。
因为类的方法都定义在prototype对象上面,因此类的新方法能够添加在prototype对象上面。Object.assign方法能够很方便地一次向类添加多个方法。
class Point { constructor(){ // ... } } Object.assign(Point.prototype, { toString(){}, toValue(){} });
prototype对象的constructor属性,直接指向“类”的自己,这与 ES5 的行为是一致的。
Point.prototype.constructor === Point // true
另外,类的内部全部定义的方法,都是不可枚举的(non-enumerable)。
七,面相对象
//es5中经过构造函数建立对象 function User (name,password){ this.name=name; this.password=password; console.log(this) } User.prototype.showname = function(){ alert(this.name) } User.prototype.showpass = function(){ alert(this.password) } var user=new User('lee','123') user.showname() user.showpass() // 以前的面相对象缺点: // 1,类和构造函数混为一谈了。 // 2.方法添加到类(构造函数)外面。
es6中建立对象
// es6 class User { //User是类 constructor(name,password){ //constructor是构造函数 this.name=name; this.password=password } //方法写在类里面,并且必须不能有function,多个方法之间不能有‘,’ showname(){ alert(this.name) }, showpass(){ alert(this.password) } } var user = new User ('wang','1234') user.showname() user.showpass()
继承:
es5中的继承:
// 继承es5 function User (name,password){ this.name=name; this.password=password } User.prototype.showname=function(){ alert(this.name) } User.prototype.showpass=function(){ alert(this.password) } function Vipuser(name,password,level){ User.call(this,name,password) this.level=level } Vipuser.prototype=new User() //先new一下 Vipuser.prototype.constructor=Vipuser;//在补一下constructor,由于constructor乱了 Vipuser.prototype.showlevel=function(){ alert(this.level) } var vipuser=new Vipuser('LV','2323',8) vipuser.showname() vipuser.showpass() vipuser.showlevel()
es6中的继承
// 继承es6 class User{ constructor(name,password){ this.name=name; this.password=password; } showname(){ alert(this.name) } showpass(){ alert(this.password) } } class Vipuser extends User { constructor(name,password,level){ super(name,password)//super,超类,父类。执行父级的构造函数。继承父级的属性,至关于es5继承中的User.call(this,name,password) this.level=level } showlevel(){ alert(this.level) } } var vipuser=new Vipuser('dawang','123',9) vipuser.showname() vipuser.showpass() vipuser.showlevel()