js面向对象编程及函数进阶、ES6

一、面向对象编程

  1. 对象:包括字符串 数组 自定义对象等,万物皆对象

    对象是单个事物的抽象,包含属性和行为(方法,功能)

    一个容器,封装了属性和方法,面向对象,就是将属性和方法封装到几个对象中;

    要用的话,直接调用就可以

    面向对象的特性:封装性 继承性 【多态性】抽象

2.打印学生的成绩,使用对象的方法

3.面向对象的设计思想

抽象除Class(构造函数) 根据Class(构造函数)创建Instance(实例) 指挥Instance得结果

4.创建对象的几种方式

方法1.

var a=new Object();     这样有一点麻烦

a.name="Bob";

a.age=18;

a.sayName=function(){

}

方法2.

var a={name:"Bob";age:18;sayName:function(){   }   }

​ 字面量方法容易重复

方法3.封装一个工厂函数

方法4.自定义构造函数

5.实例成员:在构造函数内部添加给this的成员,在创建实例是必须由对象调用

静态成员:添加构造函数自身的成员,只能由构造函数调用 如: Math.PI 调用圆周率

6.多个对象中有公共的属性,每次调用会浪费内存,解决方法一:

将公共的函数提前到构造函数之外:


function sayName(){`

            `console.log(this.name);`

                `}`

`function Person(name,age,sex)    {`
    this.name=name;    --用this替代新对象person,不需要创建一个空对象Object`
    this.age=age;
    this.type=human;
    this.sayName=sayName;}

`var p1= new Person(“zs”,18,true);`

`p1.sayName ();`

解决方法二:将公共的函数封装到一个对象中;然后在后面继续写构造函数

var fns={sayName:function() {
    console.log(this.name)}; 

sayAge:function() {
    console.log(this.age)};

}

7.解决方法三:使用原型对象:prototype对象,可以解决内存浪费问题

1.任何函数都有prototype属性:构造函数也不例外,属性值是一个对象,通常叫做

原型对象,这个对象内部可以添加一些属性和方法:比如上面构造函数Person进行操作

添加属性和方法:

 Person.prototype.type="human" ;Person.prototype.sayHi=function(){ 函数内容};

构造函数的原型对象的constructor属性(对象都有这个属性):指向自己Person这个构造函数:

console.log(**Person.prototype.constructor** ) 输出为Person这个函数

8.所有的对象都有——proto——_的属性:是一个指针,指向的就是生成实例对象的构造函数的原型对象,即 实例对象的——proto——属性指向的是这个实例对象的原构造函数的prototype属性

——proto——属性不是一个标准的属性,是浏览器自己根据语法自动生成的

p1.——proto——.saiHi()=Person.prototype.saiHi() ,不过一般简写成p1.sayHi() 即可

同样p1.——proto——.constructor=p1.constructor ,输出是构造函数Person

9.每个构造函数的prototype属性指向他的原型对象,而这个原型对象的所有属性和方法,都会被构造函数的实例对象所拥有,因此:

我们可以把实例对象需要共享的属性和方法直接定义在prototype对象上,优化解决方法二:将所有实例共享的属性和方法,如之前构造函数中的type=human属性 以及sayName 方法,都添加给原型对象:

Person.prototype.type=human;

Person.prototype.sayName=function(){console.log(this.name)};

p1.sayName();   
//调用即可

Person构造函数的原型对象也是对象,是对象就有原型对象,所以生成的实例对象的原型对象的原型对象是一个叫做Object函数的原型对象,而这个Object的原型对象指向的是一个空指针null;关系图,也就是原型链:
原型链.png

10.原型链:

原型对象.png

12.实例对象读写原型对象成员

先在自己身上找,找不到的话会沿着原型链向上查找,如果一直到原型链的末端都没有找到,则返回undefined;

通过实例对象更改或添加原型对象的属性和方法,会直接添加给自己,会屏蔽对原型对象的访问

通过实例对象更改或添加原型对象中复杂类型的数据,如果自己没有找到,会沿着原型链继续查找:

添加一个性别属性 p1.sex="male",然后console.dir(p1) 会在自己身上找到,但是在自己的原型对象之上没有这个新加的属性 ;

如添加一个方法: p1.sayAge=function(){} 同样输出console.dir(p1) ,这个方法只添加在自己身上

如果通过实例对象更改原型对象的属性和方法:

p1.type="person" (原来是 human),同样输出console.dir(p1), 发现这个实例对象增加了一个type="person"的属性,而他的原型对象p1.--ptoto-- 上的type="human"还是没有变化,添加方法也一样的

如果添加一个新的属性给原型对象,是一个对象类型,city:beijng,

如:``

Person.prototype.address={city:"beijing"}

通过实例对象更改:

p1.address.city="shanghai " 会在原型链中进行查找,

输出p1这个实例对象,会发现在自己身上没有修改,而是修改到p1的实例对象p1.--proto--上了

13.更简单的原型语法

每添加一个属性和方法都要写一Person.prototype,比较麻烦,因此使用一个对象字面量 对

对象原型进行赋值 ,如:

Person.prototype={

    type:"human";

    sayName:function(){console.log(this.name)}

}

这样会丢失constractor成员,因此要手动添加contractor属性指向正确的构造函数

Person.prototype`={
type:"human",   
constractor:Person,    手动添加contractor属性指向正确的构造函数
sayName:function(){console.log(this.name)};}

一般将私有成员(非函数)放到构造函数中,将公共成员(一般是函数)放到原型对象中,充

重置了Person.prototype 记得手动添加contractor属性指向正确的构造函数

14.内置构造函数的原型对象

Object.prototype Function.prototype String.prototype Array.prototype

一般不允许更改内置而构造函数的原型对象

15.自调用函数IIFE:表示函数在定义时就立即调用,如果将一个函数矮化成一个表达式,就可以在后面添加()进行调用

通过在函数前面添加操作符,可以让函数矮化成表达式 如+ - ()! 四种方式、

最常用的是(),将函数用()包裹起来,可以关住函数的作用域,在外部是不能进行

调用的

(function(a) {

console.log(a)} (1); 用完之后后面就用不了了,类似于一次性函数,后面括号里

是实参,输出为 1

补充:函数调用的方式,函数名或函数表达式后加(),自调用函数就是一个函数表达式加了

(),这个函数表达式用()把原函数矮化成立表达式

自调用函数可以封闭作用域

二、随机方块

1.以对象字面量的方式,封装一个tools对象,里面添加获取随机整数的方法getRandom,,MDN上有,直接用

在添加一个获取随机颜色的方法:给rgb(r,g,b)中的r g b获取0-255的随机值,调用getRandom

方法,用this调用,最后返回一个颜色值: return "rgb("+r+""+g+""+b+")"

2.自定义一个 随机方块 的构造函数,设置方块的宽高 背景颜色 定位位置

function Block(  option){     参数option是一个对象,里面包含宽高 颜色 定位等方块的属性

    option=option||{ };

    this.width=option.width||20;

    this.height=option.height||20;

    this.backgroundColor=option.backgroundColor||"red";

    this.x=option.x||0;

    this.y=option.y||0;

}

三、贪吃蛇

1.创造Food的构造函数,设置宽高 位置 颜色 的属性

通过原型设置方法,将事物渲染到大的盒子map上

onkeydown 按键按下事件 键盘编码 37:左键

38:上键 39:右键 40:下键

window.Food=Food

利用window对象增加一个food属性,属性值就是food的构造函数,可以把food 的构造函数在外部进行调用,在外部生成对象实例

2.思路:根据游戏抽象出几个必要的对象,Food Snake Game对象,其中Game对象并不是游戏中的必备元素,而是游戏的逻辑,都可以抽象出对象。
3.Food 对象:构造函数生成对象,设置宽高、left、top的基本属性;在其原型对象上添加渲染render方法,使其能够随机出现在地图中,需要使用空数组存储所有会出现的Food,保障后期随时生成和删除;
同样在其原型对象上添加删除remove方法。

function Food(){
    // 自身高度,宽度,默认的位置,颜色
    this.width=20;
    this.height=20;
    this.x=0;
    this.y=0;
    this.color="green";
    // 创建一个空数组容纳随机生成的食物块
    this.elements=[];  
}

4.Snake 对象:构造函数生成对象,注意设置的每一个蛇节的css属性,在其原型对象上添加渲染方法,注意是一节一节渲染,初始3节,循环渲染,渲染后需要删除重新渲染;

Snake.prototype.render=function(map){
    // 循环的方法进行渲染
    for (var i = 0,len = this.body.length ; i < len ; i++){
        var ele=document.createElement("div");
        // 给生成的每一个ele添加样式;
        ele.style.width = this.width+"px";
        ele.style.height=this.height+"px";
        ele.style.left=this.body[i].x*this.width+"px";
        ele.style.top=this.body[i].y*this.height+"px";
        ele.style.backgroundColor=this.body[i].color;
        ele.style.position= "absolute";
        map.appendChild(ele);
        this.elements.push(ele);
    }
};

在原型对象上添加位置食物方法 move,及初始方向向右移动,当前一节的位置left top等于前一节的位置

for(var i=this.body.length-1;i>0;i--){
        this.body[i].x=this.body[i-1].x;
        this.body[i].y=this.body[i-1].y;
    }

5.Game对象,最重要的一步,决定游戏是否成功运行的关键,需要首先在其构造函数中设置Food 和Snake属性,生成相应的对象实例,后续直接调用。

var that;
function Game(map){
    // 生成实例对象作为属性
    this.food = new Food();
    this.snake = new Snake();
    this.map = map;
    that = this;
}

需要用到的方法:绑定按键的bindKey:给整个文档doucument设置 onkeydown事件,利用事件委托函数控制蛇的移动方向

让蛇运动的方法:设置定时器,定时器函数参数包含 Food实例和Snake直接调用的render 和remove函数,另外需要判断蛇吃到食物的条件,以及吃到食物后,将蛇节最后一一项添加给蛇身自己。

  // 吃掉食物增加一个蛇节
    // 吃掉食物的条件:蛇头的位置和食物的位置重合
    for(var i=0;i<that.food.elements.length;i++){
        if(hX===that.food.elements[i].offsetLeft&&hY===that.food.elements[i].offsetTop){
            that.food.remove(that.map,i);
            that.food.render(that.map);
            var last=that.snake.body[that.snake.body.length-1];
            that.snake.body.push({
                x:last.x,
                y:last.y,
                color:last.color
            });
        }
    }

四、继承

1.对象之间的继承,父级对象继承到子级的对象,如 老李和小李两个对象

var laoli = {

   name: "laoli",

   money: 1000000,

   house: ["商铺", "住宅"],

   tech: function () {

    console.log("厨艺")

   }};
var xiaoli={

name: "xiaoli"

   对象之间进行继承,使用 for……in

   for (var k in laoli) {

 子级有的属性不需要继承

  if (xiaoli[k]) {

   continue;

   }

    xiaoli[k] = laoli[k];

  }}

封装一个对象之间继承的函数

function extend(parent, child) {

   for (var k in parent) {

    // 子级有的属性不需要继承

    if (child[k]) {

     continue;

    }

    child[k] = parent[k];

   }

  }

  // 调用函数实现继承

  extend(laoli,xiaoli);

  console.log(xiaoli);

2.原型继承

提取两个对象所有公共的属性,然后放到一个父类型中

比如学生 和老师,公共的属性有 name age sex ,用构造函数创建一个人类

Person对象,书写两者公共的3条属性。

// 人类类型

  function Person(name,age,sex) {

   this.name = name;

   this.age = age;

   this.sex = sex;

  }

  // 学生类型

  function Student(score) {   

   this.score = score;

  }

  // 老师类型

  function Teacher(salary) {

   this.salary = salary;

  }

  // 原型对象,可以将自己的属性和方法继承给将来的实例对象使用

  Student.prototype = new Person("zs",18,"男");

  Student.prototype.constructor = Student;

  // 生成一个实例

  var s1 = new Student(89);

  var s2 = new Student(100);

  console.dir(s1);

  console.dir(s2);

  console.log(s1.name);

  console.log(s1.constructor);


  1. call方法
// call

  // 函数本身就是一种对象,就能够有自己的属性和方法

  // call 方法本身是一种执行函数的方法

  function fn(a,b) {

   console.log(this);

   console.log(a + b);

  }
// 自定义一个简单对象 o
  var o = {     

   name: "zs"

  }

  // 普通函数调用

  // fn(2,3);

  // call 方法在调用函数的时候,有两个功能

  // 1.更改函数内部的 this 指向,原先默认是 window

  // 2.调用函数执行内部代码

  // 参数: 第一个参数用来指定 this,第二个及以后,就是传的实参

  fn.call(o,3,4);

4.属性的继承

// 构造函数的属性的继承

  // 人类类型

  function Person(name,age,sex) {

   this.name = name;

   this.age = age;

   this.sex = sex;

  }

  // 学生类型

  function Student(name,age,sex,score) {

   // 直接对父类型的构造函数进行一个普通调用

   // Person 普通调用过程中,内部的 this 指向的是 window

   // 可以通过 call 方法更改Person 内部的 this,现在的this就指向了Student

   Person.call(this,name,age,sex); 

   this.score = score;

  }

  // 老师类型

  function Teacher(name,age,sex,salary) {

   Person.call(this,name,age,sex); 

   this.salary = salary;

  }

  // 创建学生的实例对象

  var s1 = new Student("zs",18,"男",89);

  var s2 = new Student("ls",19,"男",92);

  console.dir(s1);

  console.dir(s2);
  1. 方法 的继承

    // 父类型的原型对象中有方法也需要继承
    
      Person.prototype.sayHi = function () {
    
       console.log("你好");
    
      };
    
      // 学生类型
    
      function Student(name,age,sex,score) {
    
       Person.call(this,name,age,sex); 
    
       this.score = score;
    
      }
    
      // 子类型的原型对象上,需要继承父类型原型对象的方法
    
      // 方法1:对象拷贝继承
    
      // for (var k in Person.prototype) {
    
      //  // 保留自己的 constructor 不要进行继承
    
      //  if (k === "constructor") {
    
      //   continue;
    
      //  }
    
      //  Student.prototype[k] = Person.prototype[k];
    
      // } 
    
    
    
      // 方法2:原型继承
    
      Student.prototype = new Person();
    
      Student.prototype.constructor = Student;
    
      
    
      // 老师类型
    
      function Teacher(name,age,sex,salary) {
    
       Person.call(this,name,age,sex); 
    
       this.salary = salary;
    
      }
    
      // 创建学生的实例对象
    
      var s1 = new Student("zs",18,"男",89);
    
      var s2 = new Student("ls",19,"男",92);
    
      console.dir(s1);
    
      console.dir(s2);
    
      s1.sayHi();
    

6.组合继承,属性在构造函数中继承,方法通过原型继承,参照4 5的结合

// 组合继承:属性在构造函数内部继承,方法通过原型继承

  function Person(name,age) {

   this.name = name;

   this.age = age;

  }

  Person.prototype.sayHi = function () {

   console.log("你好");

  }

  // 生成一个子类型

  function Teacher(name,age,salary) {

   // 继承父类的属性

   Person.call(this,name,age);

   this.salary = salary;

  }

  // 方法继承,通过原型对象继承

  Teacher.prototype = new Person();

  Teacher.prototype.constructor = Teacher;

  // 生成老师的一个实例

  var t1 = new Teacher("wang",45,10000);

  console.dir(t1);

  console.log(t1.name);

  t1.sayHi();

继承的原理,方法为什么可以被继承:

继承的原理,原型链方法.jpg

7.函数定义的方式

函数表达式可以没有名字,如匿名函数,函数定义和声明的区别:

fun(); 函数调用

  // 函数声明

  // 必须定义函数名

   function fun() {

  / console.log(1);

   }

  // 函数表达式

  // 是将函数赋值给一个变量,可以是一个匿名函数

   var fn = function () {

    console.log(2);

   };
   fn();

8.函数定义的new方式

函数本身也是一种对象,通过new Function 的方式来定义的,如:

var fun = new Function('a','b','var a = "1";console.log(a+b)');

  fun(2,3);

  console.dir(fun);

传的参数都 是字符串 ,要用单引号或者双引号,一般不推荐,还是用传统的函数声明调用方式

函数本身也是一种对象,因此可以调用属性和方法

9.函数的调用和this

普通函数内的this默认指向window;构造函数中this指向的是将来创建的实例对象;

对象中的方法,里面的对象指的是调用的对象自己;事件函数的内部 this 指向的是事件源,

如 btn ,document;定时器和延时器中的函数,默认内部的 this 指向的是 window
不同类型函数内部this指向.jpg

this真正指向的是谁,要看其调用形式和执行环境

10.函数的 call apply bind 方法,都可以打点调用,如 fun.call( 参数) fun是函数名,括号

function fun(a,b,c,d) {

   console.log(this);

   console.log(a + b + c + d);

  }
  // call方法:1.功能:第一个可以指定函数的 this,第二个可以执行函数并传参

  // 2.参数:第一个参数,传入一个指定让 this 指向的对象,第二个参数及以后,是函数参数的列表

  // 3.返回值:就是函数自己的返回值

  // 4.测试

  var o = {

   name: "zs"

  }

  // fun.call(o,1,2);



  // apply 方法

  // 1.功能:第一个可以指定函数的 this,第二个可以执行函数并传参

  // 2.参数:第一个参数,传入一个指定让 this 指向的对象,第二个参数是函数的参数组成的数组

  // 3.返回值:就是函数自己的返回值

  // 4.测试

  // fun.apply(o,[4,5]);



  // bind 方法

  // 1.功能:第一个可以指定函数的 this,bind 方法不能执行函数,但是可以传参

  // 2.参数:第一个参数,传入一个指定让 this 指向的对象,第二个参数及以后,是函数参数的列表

  // 3.返回值:返回一个新的指定了 this 的函数,也可以叫绑定函数

  // 4.测试

  var fn = fun.bind(o,2,3);

  console.log(fn);

  fn(6,7);  输出为2+3+6+7=18,前提是原函数fun传入4个参数才可以
  1. call方法的运用

如何让一个字面量书写的对象,像数组一样,直接调用 arr.push() 方法添加新的元素呢?

// {} 的对象自己是没有 push 方法的

  // 类数组对象 getElementsByTagName

  var o = {

   0: 10,

   1: 20,

   2: 30,

   length: 3

  };

  // console.log(o[0])

  // 增加一项新的数据,原先老实方法

  // o["3"] = 40;

  // o.length = 4;

  // 利用数组中的 push 方法,指定内部的this 为对象 o,就可以处理类数组对象的数据

  Array.prototype.push.call(o,50);

  console.log(o);    
  // 输出为:50添加到了o 对象的第四项上了
  1. apply方法的运用

内置在js中的方法比如Math的一些方法,如何将数组作为参数呢?

// 定义一个数组,利用 apply 方法,可以将它拆开进行操作

  var arr = [1,3,4,6,8];



  // 想借用一些现在内置在js 中的方法

  // console.log(Math.max(1,3,5,7,9));



  // 利用 apply 方法,将数组传给 max 的第二个参数

  // console.log(Math.max.apply(Math,arr));



  console.log(1,2,3);

  console.log.apply(console,arr);  ---输出为 1 3 4 6 8分开的数字而非数组


  1. bind方法的运用
// 想修改的是定时器的函数内部的 this

  var o = {

   name: "zs",

   age: 18,

   s: function () {

    setInterval(function () {

     console.log(this.age);

    }.bind(this),1000);

   }

  }

  // o.s();

  // 更改 事件函数中的 this

  document.onclick = function () {

   console.log(this);

  }.bind(o);  输出的是o这个对象,不执行定时器这个函数
  1. 函数的其他成员

    console.dir一个函数,可以得到函数内部的成员

    arguments:传入的是函数在调用 时,传入的所有实参组成的类数组对象,有一个callee属性,类似constructor,

    caller 函数的调用者,函数在哪个作用域调用,caller就是谁,如果是在全局调用,caller就是null

    如果是在内部执行的话,如:

    function test() {
    
       fn(1,2,3,4);
    
      }
    
      test();
    

    此时caller就是调用的函数名称 test

    length:形参的个数

    name:函数的名字

    使用arguments对象,找到最大的一个实参,如:

    function max() {
    
       // 判断实参中最大的数
    
       var nowMax = arguments[0];
    
       for (var i = 1 ; i < arguments.length;i++) {
    
        if (arguments[i] > nowMax) {
    
         nowMax = arguments[i];
    
        }
    
       }
    
       return nowMax;
    
      }
    
      console.log(max(1,4,7,9));
    
  1. 高阶函数:函数可以做参数的函数;函数可以作为返回值的函数

    1.函数作为另一个函数的参数时:

    // 定义一个函数,吃饭的函数,吃完饭之后,可以做其他的事情,看电影、聊天、看书
    
       function eat(fn) {
    
        console.log("吃晚饭");
    
      //  // 接下来的要做的事情是不固定的
    
       fn();
    
       }
    
       eat(function () {
    
       console.log("看电影");
    
      });      输出结果是 吃晚饭 看电影
    

    2.函数作为另一个函数的返回值

    需求:通过同一段代码实现以下效果
    
      // 输出 100 + m
    
      // 输出 1000 + m
    
      // 输出 10000 + m
    
      function outer(n) {
    
       return function inner(m) {
    
        console.log(m + n);
    
       }
    
      }
    
      // 在外部执行 inner 函数
    
      // 100 + m
    
      var fun = outer(100);
    
      fun(3);    输出为103.
    
      fun(13);   输出为113 
    
      fun(23);    输出为123
    
      var fun1 = outer(1000);
    
      fun1(3);   输出为1000+3=1003
    
  1. 函数闭包:天生存在的,函数记住自己的作用域和函数自己,函数自己也

    就是一个闭包,不论函数以何种方式进行调用,都会回到自己定义时的密闭

    环境进行执行:即如果函数调用时跑到外部进行调用,也会执行原先在内部生

    成的语句

体会一下闭包,把内部函数拿到外部父函数外面,看能不能调用父函数内部的变量:

 

```
// 将一个内部函数拿到父函数的外面,观察是否还能调用父函数内部的变量

  function outer() {

   var a = 10;

   function inner() {

    console.log(a);

   }

   // 将inner 函数作为返回值

   return inner;

  }

  outer()

  // 在outer函数的外面,是不能直接访问 a 变量,如下面两条语句就无法执行

  // outer();

  // console.log(a);

  // 将 outer 执行的结果,赋值给一个变量

   var inn = outer();

  console.log(inn);

  // 在全局调用 inn,按道理应该查找全局的 a变量

  inn();

  // 输出的真正结果是 10,来自于 outer 函数内部的变量
```
  1. 闭包功能:1.可以在函数外部读取函数内部成员

    ​ 2.让函数在外部延长函数内部变量的存活时间,不会被马上消除,如:

    // 将一个内部函数拿到父函数的外面,观察是否还能调用父函数内部的变量
    
      function outer() {
    
       // 形成闭包环境中的变量不是一成不变的,可以被更改
    
       var a = 10;
    
       function inner() {
    
        console.log(a++);
    
       }
    
       // 将inner 函数作为返回值
    
       return inner;
    
      }
    
      var inn = outer();
    
      inn();     输出结果为10
    
      inn();     变量a未消失,且可以被改变,此时的a变成了11,所以输出为11
    

    本身变量a在执行一次就在内存中被销毁了,因为闭包,所有这个变量可以被调用两次

  2. 闭包带来的问题:使用自调用函数解决

五、正则表达式

1.在线正则表达式训练网站

c.runoob.com/front-end/845

2.创建正则的方法

正则表达式也是对象,是一种索引类型 ,创建方法2种:推荐使用字面量方式

// 创建正则的第一种方法,正则的字面量 /

  var reg = /abc/;

  // 第二种,通过 构造函数 创建

  var reg1 = new RegExp("cde");

3.相关正则方法

字符串方法与其对应的正则表达式:

4.正则表达式的组成

六、 ES6 新特性 习惯将ES2015称为ES6

1.解决了原有语法的一些问题或者缺陷;对原有语法进行增强;

全新的对象,全新的方法,全新的功能;全新的数据类型和数据结构

2.最新版本的浏览器,谷歌最新版,可以直接在浏览器中执行;可以在VS中安装相关插件

3.let和块级作用域

通过现代关键字let定义块内部的变量 其定义的变量在块级作用域内部可以被访问

非常适合设置 在for 循环中的循环变量:如:

  // 非常适合设置 在 for 循环中的循环变量

  for (var i = 0 ; i < 3 ; i++) {

   for (var i = 0; i < 3;i++) {

    console.log(i);

   }
 
  }    输出一次 0 1 2

因为for 里面的变量都是全局变量,变量间会发生覆盖,最终值执行了一次外层循环,三次内层循环

没有输出9个,输出 0 1 2

let设置:

  // 通过 let 定义变量,只在自己的循环中生效

  for (let i = 0 ; i < 3 ; i++) {

   for (let i = 0; i < 3;i++) {

    console.log(i);

   }

  }

此时就会输出3次 0 1 2

通过循环批量添加事件,之前的用法:

  // 通过 let 定义变量,只能在块级内部被调用

  var eles = [{}, {}, {}];

  for (var i = 0 ; i < eles.length ; i++) {

   eles[i].onclick = function () {

    console.log(i);

   }

  }

  eles[0].onclick();

最终结果,不管传入的实参是0 还是 1 2,结果都是3 ,因为for循环内部var 定义的是全局变量,循环开始后 全局变量 i先后为 0 1 2,最终 i的值会由最新的 2替换,再i++为3,最终输出一个 3

解决方法:将var 替换成let, 或者用一个自调用函数封闭i的作用域

循环:实际有两层作用域

for (var i = 0 ; i < 10 ; i++) {

   var i = "foo";

   console.log(i);     只输出了一次foo
 for (var i = 0 ; i < 10 ; i++) {

   let i = "foo";

   console.log(i);       输出了10次foo  ,for循环括号里的var也可以换成let

4.const

const name=“zs”;

name=“ls”;

​ 此时name不会被更改为ls。const声明的时候必须同时赋予一个初始值

const obj={};

obj.name="zs"; 给这个空对象添加属性和属性值是可以的,这个obj指向的对象是没有改变的

补充:主要用const,配合let,不用var

5.数组的解构

  // 数组解构

  const arr = [100, 200, 300]

  const foo = arr[0]

  const bar = arr[1]

  const baz = arr[2]

  console.log(foo, bar, baz)
新的方法:`

  const arr = [100, 200, 300]`

  const [foo, bar, baz] = arr`

  console.log(foo, bar, baz)`  输出为100 200 300,对应赋值

如果只想要获取第三个成员,可以写成 const [, , baz] = arr

获取成员后的剩余成员:

    const arr = [100, 200, 300]

  const [foo, ...rest] = arr

  console.log(rest)

数组的灵活运用:

const arr = [100, 200, 300]

  const [foo, bar, baz = 400, more = 123] = arr

  console.log(more)   输出为123

  console.log(baz)   输出为400而不是300

将字符串分割为数组,用数组的解构,如:

  const path = "foo/bar/baz"

  // const temp = path.split("/")   之前的方法,获取下标为1的项 bar

  // const a = temp[1]

  const [,a,] = path.split("/")

  console.log(a)

6.对象的解构,同数组的解构很像:

  // 对象解构

  const obj = { name: 'zs', age: 18 }

  const { name } = obj

  console.log(name)   获取name 的属性和值


  const obj = { name: 'zs', age: 18 }
  const name = "tom"

  const { name: newName  } = obj

  console.log(name)  输出tom

  console.log(newName)  输出为zs

7.模板字符串,加反引号`

添加反引号可以给字符串换行,

${ }插值表达式 可以将里面的内容插入到字符串中,如:

  const name = "tom"

  const str = `hey, ${name},${1 + 1},${Math.random()}`

  console.log(str)   输出为hey,tom,2,一个随机数

8.模板字符串标签函数

 const name = "zs"

  const gender = true

  function myTagFunc(strings, name, gender) {

   // console.log(strings,name,gender)

   // 处理一下 性别

   const sex = gender ? "man" : "woman"

   return strings[0] + name + strings[1] + sex + strings[2]

  }

  const str = myTagFunc`hi, ${name} is a ${gender}`

  console.log(str)

9.字符串扩展方法

  const msg = 'Error: foo is not defined.'    

  console.log(msg.startsWith('Error'))    判断是否以xx开头

  console.log(msg.endsWith('.'))        判断是否以xx结尾

  console.log(msg.includes('foo'))   判断是否包含xx

10.参数默认值

形参设置默认值,实参没有设置,传入的就是默认值

  function foo(enable = true,bar) {    参数默认值在前  

   // enable = enable || true

   // enable = enable === undefined ? true : enable

   console.log('foo invoked enable:')

   console.log(enable)

  }

  foo('bar')    输出为foo invoked enable:  bar,因为此时默认值被实参‘bar’替代了
  function foo(bar,enable = true) {      一般都是参数默认值在后

   // enable = enable || true

   // enable = enable === undefined ? true : enable

   console.log('foo invoked enable:')

   console.log(enable)

  }

  foo('bar')      输出为 foo invoked enable:   true
9.剩余操作符

  function fun(...args) {

   console.log(args)

  }

  fun(1,2,3,4)     输出为所有实参的数组 [1,2,3,4]
  function fun(n,...args) {

   console.log(args)

  }
   
  fun(1,2,3,4)     输出为除第一个实参1的剩下实参的数组  [2,3,4]

11.展开操作符

  // 展开数组操作

  const arr = ['foo', 'bar', 'baz']

  // console.log(arr[0],arr[1],arr[2])   传统的死办法

  // console.log.apply(console,arr)   利用apply 的方法,console对象的this指向的就是console自己



  console.log(...arr)     es2015的方法,输出为[foo,bar,baz]

12.箭头函数 =>

将下面这个函数用箭头函数改写:

   function plus(a) {

   return a + 1

  }

  console.log(plus(10))

改写为:

  const plus = (a, b) => {   只要一个参数可以不用加(),如果箭头后只有

   console.log('plus invoked')       一条语句,那这条语句就是返回值

   return a + b

  }

  console.log(plus(1,2))    输出为3
  const arr = [1,2,3,4,5,6,7]

  // const arr1 = arr.filter(function (item) {  筛选数组中的奇数,filter筛选函数

  //  return item % 2   只有奇数才会返回1,也就是true

  // })

  const arr1 = arr.filter(i => i % 2)   用箭头函数简化上面的筛选函数

  console.log(arr1)

12.箭头函数的this 内部没有this 的机制,从外部进行查找

  // 箭头函数与 this

  const person = {

   name: "tom",

   // sayHi: function () {

   //  console.log(`hi,my name is ${this.name}`) 这里的this指向的是person对象自己

   // }

   // sayHi: () => {

   //  console.log(`hi,my name is ${this.name}`)   箭头函数这里的this找不到指向的内容

   // }

   sayHi: function () {

​    setTimeout(() => {      箭头函数省去了函数名

​     console.log(`hi,my name is ${this.name}`)  箭头函数本身没有this,从外部找到了this,指向的是person,如果没有用箭头函数,则无法正确执行

​    },1000);

   }

  }

  person.sayHi()    可以正确执行


13.对象字面量的增强

 // 对象字面量增强

  const bar = "bar"

  const age = "age"

  const obj = {

   name: "tom",

   // bar: bar

   bar,     属性名和属性值一样可以省去属性值

   sayHi () {       方法可以不用写函数名

​    console.log('hi')

​    console.log(this)     this指向的是对象obj

   },

   // 计算属性名

[1+2]: 18       添加表达式作为属性名,[]里面可以是表达式也可以是变量

  }

  // obj[age] = 18    以前的方法。obj添加[]进行更改。[]内部是动态的属性名,乐意是表达式

  console.log(obj)

  // obj.sayHi()

14.对象扩展的方法 Object.assign方法

 const source1 = {

   a: 123,

   b: 123

  }

  const source2 = {

   b: 678,

   d: 789

  }

  const target = {

   a:456,

   c:789

  }

  const result = Object.assign(target,source1,source2)   将后面两个对象的属性合并到target中去,同样的属性会采用后面对象的属性值进行覆盖

  console.log(target)
 
  console.log(target === result)    最终的新对象还是target对象
  // 应用,在 options 对象参数接收时,简化

  function Block(options) {

   // this.width = options.width;  以前的方法,通过传入的实参添加Blcck的属性

   Object.assign(this,options)

  }

  const block1 = new Block({width: 100, height: 100, x: 50, y: 50})

  console.log(block1)

15.Object.is方法 :判断两个是否是同一类型的数据

Object.is(NaN,NaN) 值为false

16.class

function Person(name, age) {     之前构造函数的方法

  //  this.name = name;

  //  this.age = age;

  // }

  // Person.prototype.sayHi = function () {

  //  console.log(`hi,my name is ${this.name}`)

  // }



  class Person {       class的方法,也更加整洁

   constructor (name, age) {      参数在constractor后书写

    this.name = name;

    this.age = age;

   }

   sayHi () {

    console.log(`hi,my name is ${this.name}`)

   }

  }

  const p1 = new Person("tom",18)     同样的生成p

  console.log(p1)

  p1.sayHi()    实例对象调用方法

17.静态方法 static
使用static修饰的属性和方法,可以直接由对象进行调用,生成的对象实例反而不能调用成功

class Person {

   constructor (name, age) {

​    this.name = name;

​    this.age = age;

   }

   sayHi () {

​    console.log(`hi,my name is ${this.name}`)

   } 

   static create (name,age) {

​    console.log(this)    this指向的是不是实例对象,而是Person

​    return new Person(name,age)

   }   

  }

  const p1 = Person.create("zs",19)    调用时打点是crrate  不是static

  console.log(p1)
  1. 类的继承 使用extends
// 静态方法

  class Person {

   constructor (name, age) {

​    this.name = name;

​    this.age = age;

   }

   sayHi () {

​    console.log(`hi,my name is ${this.name}`)

   }  

  }

  class Student extends Person {

   constructor (name,age,number) {

​    super(name,age)    super对象指向的是父类

​    this.number = number;

   }

   hello () {      给Student添加自己的一个方法

    super.sayHi()       使用的是父类的方法,用super打点调用父类的方法

    console.log(`学号是 ${this.number}`)   自己的方法的表达式

   }

  }

  const s1 = new Student("tom",18,102)

  s1.hello();
  1. set 的全新结构,Set是一个对象

    const s = new Set()   生成 s 这一个实例对象
    
      s.add(1).add(2).add(3).add(4).add(2)    重复的内容会被忽略
    
      console.log(s)
    

    s.forEach(i => console.log(i)) ES5中的遍历方法 for each

    `for (let i of s) {`    ES2015的遍历方法 for of
    
       `console.log(i)`
    
      `}`
    

    console.log(s.size) 可以得到集合的长度

    判断集合中是否存在某个值console.log(s.has(4))

      console.log(s.delete(100))   判断是否删除成功
    
      console.log(s)     输出为true或者flase
    

    s.clear() 删除集合中所有数据

    set一般用作数组去重:

      // 数组去重
    
      const arr = [1.3,4,6,2,4,7,5,8]
    
      // const b = Array.from(new Set(arr))  方法一:把集合转换为数组 Array.from方法
    
      const b = [...new Set(arr)]      方法2: ...rest方法
    
      console.log(b)21
    
  2. Map数据结构
    
      const obj = {}
    
      obj[true] = "boolean"
    
      obj[123] = "number"
    
      obj[{a: 1}] = "object"  对象型的数据,此时无法转换成字符串的键值对集合
    
    
    
      console.log(Object.keys(obj))  key可以让对象中的属性和属性值转换字符串的键值对集合
    
      console.log(obj[{}])
    
      console.log(obj['[object Object]'])
    

    使用Map让对象类型也可以转换为字符串的键值对集合:

    const map = new Map()
    
      const a = { a: 1}
    
      map.set(a,100)    map 的set方法,a参数指代对象类型的属性,100是对象类型的属性的属性值
    
      console.log(map)
    
      console.log(map.get(a))
    

    map 也有 has delete clear方法

    1. symbol 本身表示独一无二的值,直接以函数方式执行 Symbol()

        `const s = Symbol()`
      
        `console.log(s)`
      
        `console.log(typeof s)`  输出为symbol 
      

      Symbol() 函数内部可以传入参数,参数就是对这个数据的描述,用作区分

      如:Symbol(” ddd“ )

      Symbol()`可以作为对象的属性,

      const obj = {
      
         [Symbol()] : 789,
      
         name: "zs"
      
        }
      
        obj[Symbol()] = 123
      
        obj[Symbol()] = 456
      
         console.log(obj[Symbol()])无法在外部进行访问
      
    2. symbol最主要的作用就是为对象添加独一无二的属性

    3. for of遍历

      之前的变量 for each for in

      const arr = [100, 200, 300, 400]
      
        for (const item of arr) {
      
         console.log(item)
      
        }
      
        arr.forEach(item => {  没有办法打断遍历
      
         console.log(item)
      
        })
      
        for (const item of arr) {
      
         console.log(item)
      
         if (item >= 200) {
      
            break;
      
         }
      
          }输出为100 200
      
       const s = new Set(["foo", "bar", "baz"])
      
        for (const item of s) {
      
         console.log(item)
      
        }
      
        const m = new Map()
                                       
        m.set("foo",1)
      
        m.set("bar",2)
                   
        for (const [key,value] of m) {
      
         console.log(key,value)
                                         输出为foo和1;bar和2的两个键值对
         }
      
        const obj = {
      
         name: "zs",
      
         age: 18
      
        }
      
        for (const item of obj) {
       
       不用于对象的遍历
          console.log(item)
      
        }
      
  3. ES 2016新增内容

```
// ES2016 新增内容

  const arr = [1,true,NaN,23,'hello']

  // console.log(arr.indexOf(true))存在会返回1

  // console.log(arr.indexOf(null))不存在会返回-1

  // console.log(arr.indexOf(NaN))无法查找NaN是否存在

  // includes 包含

  // console.log(arr.includes(NaN))包含返回true



  // 指数运算符 **
                                    
  // console.log(Math.pow(2,3))自带的方法,下面ES2016的方法更便捷
   console.log(2 ** 10)
```
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容