2

一,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()

用户bPQb0e
133 声望5 粉丝