一,let块级作用域
由于js的历史原因。javascript里的es6规范之前,只要函数作用域。当写一些大型的项目代码中,很容易产生全局变量,例如:
<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;
<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值。
现在我们可以使用let 定义块级变量。 只在代码块{}内生效;
如:
<script>
{
var a=1
}
alert(a)
</script>
弹1
<script>
{
let a=1
}
alert(a)
</script>
报错
使用块级应该注意:
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.不存在变量提升
会报错,ReferenceError
console.log(a); // 输出undefined
console.log(b); // 报错ReferenceError
console.log(c); // 报错ReferenceError
var a = 2;
let b = 2;
3.暂时性死区(TDZ)
只要进入当前块级作用域,所使用的变量已经存在了,但在声明之前都属于死区,不可进行操作。
typeof x; // ReferenceError
typeof y // undefined
let x;
4.不允许重复声明
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声明一个只读的常量。一旦声明,常量的值就不能改变
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
七.class
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()
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。