如今重新回看Es5、Es6你会不会有不一样的理解


前言

当初学习Es5、Es6时你是不是有一些疑问,关于有些用法、为什么这么用以及这样用的好处你现在搞懂了吗?奋力奔跑的同时别忘了那些可以为你加速的“小伙伴”。

ES5

1. 严格模式:

比普通的js运行更严格的机制,js语言存在很多广受诟病的缺陷,为了弥补缺陷,项目必须运行在严格模式下,在要使用严格模式的作用域顶部添加: “use strict”。严格模式下要求:

  • 1.禁止给未声明的变量赋值:
    旧js中: 在任何位置,给一个从未声明过的变量强行赋值,结果: 不但不报错!都会在全局创建该变量——导致内存泄漏
    ES5中: 只要给从未声明过的变量赋值,也会报错: xxx is not defined
    所以,今后,严格模式下,要给一个变量赋值,必须先声明!
  • 2.静默失败升级为错误:
    静默失败就是执行不成功,还不报错!——不便于调试。
    ES5中所有静默失败都会以报错方式出现。
  • 3.匿名函数自调和普通函数调用中的this不再指向window,而是undefined。
    旧js中: 一个函数调用时前边没有.,也没有new,则它的this默认指向window
    比如:
    fun()
    (function(){ ... })()
    setTimeout(function(){ ... }, ms)
    ES5中: 以上三种情况中的this都是undefined
  • 4.禁用了arguments.callee
    arguments.callee: 在函数内,自动获得正在调用的函数本身。避免递归调用函数时,造成内外函数名紧耦合,递归调用经常使用。但存在问题:递归算法效率极低,不推荐使用。不过所有的递归算法,都可用循环代替!

2. 保护对象:

防止对一个对象的属性结构和属性值进行无意义的篡改,虽然现实中对对象有很多要求,但是,对象本身毫无自保能力。

  • ES5将对象的属性重新进行了划分:

    • 命名属性: 凡是用.可以访问到的属性。
      数据属性: 实际存储属性值的属性
      访问器属性: 不实际存储属性值,仅提供对其它数据属性的保护。
    • 内部属性: 无法用.访问到的属性
      比如: isArray视频中讲到的class属性
  • 如何保护命名属性:

    • 保护数据属性:
      每个数据属性不再是一个简单的变量,而是一个缩微的小对象,每个小对象中,除了保存属性值之外,还包含三个开关!
      每个开关,可以控制对这个属性的一种操作:
      那么如何看到每个属性的缩微对象?
        var eid=Object.getOwnPropertyDescriptor(对象, "属性名")
              获取自己的属性的描述信息
        返回值: 一个对象:{
          value: 属性值, 
          writable:true,
          可 修改
          enumerable:true,
          可 遍历
          configurable:true
          可 配置
        }

    如何修改其中的一个开关呢?不能用.直接访问!

    Object.defineProperty(
      对象, 
      "属性名",
      {
        开关名: true/false... : ...
      }
    )

    强调: 为了防止重新打开已关闭的开关,只能修改前两个开关时,都要带上configurable:false 作为双保险。因为configurable:false不可逆!
    结果: 如果之后的操作违反了开关的要求,比如: 试图修改只读属性的值,再比如试图重新打开已关闭的开关,都会报错(代码运行在严格模式下)!
    强调: enumerable:false,其实只是半隐藏,只能防住for in遍历。直接用.还是可以访问到的。

问题: Object.defineProperty()一次只能修改一个属性的开关,如果要修改多个属性中的开关,则每次都要重复写一遍,太繁琐,如果要修改多个属性中的开关时:

  Object.defineProperties(
    对象, 
    {
      属性名: {
        开关: true/false... : ...
      },
      属性名: {
        ... : ...
      }
    }
  )

问题: 如果一个属性值不是不能改,也不是不能遍历,只不过这个属性的值的范围是业务自定义的。只用开关,无法如此灵活的保护属性值。

  • 访问器属性: 不实际存储属性值,而是提供对另一个数据属性的保护,数据属性的开关,无法用自定义规则灵活保护属性值。如果希望用自定义规则保护属性值就要使用访问器属性。

1.先定义一个隐姓埋名且半隐藏的数据属性,来实际存储属性值。
2.定义访问器属性来保护一个数据属性。

原理:
表面上看,访问器属性的用法和普通属性的用法完全一样。只不过触发的内部原理不同。
//只要试图获取属性值时,都自动调用访问器属性的get()
//只要试图修改属性值时,都自动调用访问器属性的set(value),并把等号后的新值,先交给value参数,先去验证。

  • 保护对象结构: 禁止对对象的属性结构进行篡改。包括三个级别:
    1. 防扩展: 禁止向已经创建好的对象中添加新属性。
      Object.preventExtensions(obj): 禁止向obj对象中添加新属性
    2. 密封: 在兼具防扩展功能的同时,又进一步禁止删除所有属性
      Object.seal(obj):禁止向obj中添加新属性,禁止删除obj中现有属性
      原理: 其实seal自动将所有属性的configurable都改为false!只密封结构,属性值还是可以修改的! 几乎多有对象都需要密封,来禁止修改结构
    3. 冻结: 在兼具密封的基础上,进一步禁止修改所有属性值
      Object.freeze(obj):禁止添加新属性,禁止删除所有属性,禁止修改所有属性的值。
      原理: 自动将所有属性的writable改为false。只有那些共用的对象,才会禁止修改属性值。

3. Object.create()

即使没有妈妈(构造函数),也能生小孩儿(创建子对象)

var child=Object.create(father, {
  //defineProperties的语法,为child添加自有属性
  属性名: {
    value: 属性值, 
    writable:true,
    enumerable: true, 
    configurable: false
  },
    ... : {
    ... ...
  }
})

其中做了3件事:1. 创建一个新对象。2. 让这个新对象继承father。3. 为这个新对象添加新属性。

4. 更换不想要的this为想要的对象(call、apply、bind)

只要函数调用时,this不是想要的,都可以用专门的函数更换为想要的对象!

  • 临时: 在调用一个函数时,临时更换其中的this为指定的对象。函数调用后,恢复原样。
    函数.call(替换this的对象, 实参值,... ...)
    其中做了3件事: 1. 调用这个函数。2. 临时替换函数中的this为()中第一个实参对象。3. 将()中从第二个实参值开始的所有剩余实参值,传递给函数作为参数值。
    如果传入函数的参数是放在数组中的,而函数却需要多个数值分别传入,就要用apply()代替call()
    apply()做4件事: 比call()多一个功能,1. 调用这个函数。2. 临时替换函数中的this为()中第一个实参对象。3. 将一个数组整体,打散为单个数值,分别传入函数中。4. 将()中从第二个实参值开始的所有剩余实参值,传递给函数作为参数值。

  • 永久: 为某一个对象,专门创建一个函数副本。其中永久绑定this为这个对象。

    • var 新函数=原函数.bind(替换this的对象)
      其中做了2件事:1. 创建一个新函数副本,功能原函数一模一样。2. 将新函数副本中的this永久指向替换this的对象。
      结果: 新函数() 时,不用再反复传入替换this的对象名。
    • var 新函数=原函数.bind(替换this的对象, 实参值)
      其中做了3件事:1. 创建一个新函数副本,功能原函数一模一样。2. 将新函数副本中的this永久指向替换this的对象。3. 将实参值,按顺序永久绑定到形参变量上。
      结果: 新函数() 时,永久绑定的参数,不用再反复传值,只需要传剩余未绑定的参数值即可。

5. 数组函数:

  • 1.判断: 判断数组内容是否符合要求。包含2种:

    • 判断是否所有元素都符合要求
    var bool=arr.every(function(value, i, arr){
      //every会在每个元素上自动调用一次这个函数
      //每次调用时: value: 会获得当前元素的值。i : 会获得当前元素的位置。arr : 会获得当前数组
      return 判断条件
    })

    原理: every()会自动遍历数组中每个元素,每遍历一个元素就自动调用回调函数一次。调用时传入指定的实参值。回调函数会返回一个判断结果。如果任意一个元素上回调函数的判断结果返回false,整个every()就返回false。除非所有元素,经过回调函数的验证,都返回true,整个every()才返回true。

    • 判断是否包含符合要求的元素
    var bool=arr.some(function(value,i,arr){
      return 判断条件
    })

    原理: 只要some碰到一个返回true的元素,整个some()就返回true。除非所有元素判断结果都为false,整个some()才返回false

  • 2.遍历: 对数组中每个元素执行相同的操作

    • arr.forEach(): 对数组中每个元素执行相同操作
      arr.forEach(
      function(value,i,arr){
        //forEach会自动在每个元素上调用一次回调函数
        //每次调用时: value: 获得当前属性值。i : 获得当前位置。arr : 获得当前数组对象
      }
      )
    • arr.map() 依次取出原数组中每个元素,执行操作后,放入新数组中返回
    var newArr=arr.map(
      //var newArr=[]; 
      //for(var i=0;i<arr.length;i++){
        function(value,i,arr){
          return 新值
        }
        //newArr[i]=新值
      //}
      //return newArr;
    )

    强调: 1. 返回新数组,所以必须用变量接住新数组。 2. 原数组中的元素值保持不变。

  • 3.过滤和汇总:

    • 过滤: 复制出数组中符合条件的元素,组成新数组返回。
    var subArr=arr.filter(
      //var subArr=[];
      //for(var i=0;i<arr.length;i++){
        function(value,i,arr){
          return 判断条件
        }
        //只有判断条件为true的元素才会被加入新数组subArr中
      //}
      //return subArr;
    )
    • 汇总: 对数组中的元素内容进行统计,得出最终结论。以求和为例:
    var sum=arr.reduce(
      function(prev, value, i, arr){
        //prev是截止到当前位置之前的临时汇总值
        return prev+value
      },
      起始值
    )

ES6:

1. let:

代替var用于声明变量。

  • var的缺点:1. 会被声明提前。2. 没有块级作用域。
  • let的优点:1. 阻止了声明提前。2. 让程序块也变成了作用域。比如:
if(){... }else if(){... }else{ ... }  
for/while(){ ... }  
do{ ... }while; 

原理:let其实就是一个匿名函数自调+改名

let a; 
        ... 
翻译: 
(function(){
  var _a=xxx;
  ...
})()

let小脾气:1. 同一作用域内,不允许同时let多个同名变量。2. 在当前作用域内,let a之前,不允许提前使用a变量

  • let的兄弟: const:
    const专门用于声明一个常量: 一旦初始化后,值不可改变!
    其余特点和let是完全一样!

    优化: 优先使用const,只有明确需要频繁改变的值,才用let。
    因为所有const声明的常量,都集中存储在一个固定的区域中。js引擎查找变量时,优先查找常量区域。

2. 箭头(arrow)函数:

对一切匿名函数或回调函数的简化写法。所有匿名函数和回调函数都可以写成箭头函数

  • 简化三步骤:
    • 去掉function,在()和{}之间加=>
    • 如果只有一个形参,可省略()
    • 如果函数体只有一句话,可省略{}。如果唯一的这句话还是return则必须去掉 return。
    强调: 如果去掉{}后,注意去掉语句后的分号!
  • 箭头函数的独有特点:
    • 函数内的this与函数外的this自动保持一致!
    • 如果希望函数内的this和函数外的this保持一致时,就可转为箭头函数。
    • 如果反而希望函数内外的this不一致,就不能用箭头函数,比如: 对象中的方法不能用=>

3. for of

简化遍历数组的一种方式

  • 遍历总结:
    • 遍历索引数组: 4种:
      1.for(var i=0;i<arr.length;i++)
      优点: 可随意控制遍历的方向,步调
      2.arr.forEach()
      普通顺序遍历时,对原数组进行操作时
      3.arr.map()
      保护原数组不变,返回修改后的新数组
      4.for of: 依次遍历数组中每个值
      强调: 只能获得属性值,无法获得位置i。仅关心元素值,不关心位置时使用。
    • 遍历关联数组和对象:只能用for in
      for in不能用来遍历索引数组或类数组对象。
      因为in查找范围不仅包含当前对象的所有下标,还会遍历父对象的所有可以遍历的成员

      总结:
      1.只要是数字下标的,都可用for循环和for of遍历。
      2.只要是自定义名称下标的,都只能用for in遍历。

4. 参数增强:

  • 参数默认值(default): 即使没有传入实参值,形参变量也可提前定义一个备用值。 在定义函数时:
    function fun(形参1, ... ,最后一个形参=默认值)
    调用时,如果提供了最后一个实参值,则使用提供的实参值。如果没有提供最后一个实参值,则自动采用默认值为最后一个实参值。如果只有最后一个形参不确定,才可用默认值。如果多个形参都不确定,不能用默认值!

  • 剩余(rest)参数:代替arguments来实现重载效果。
    arguments的3个缺点:

    • 不是纯正的数组类型,不能使用数组加的函数
    • 只能获得所有实参值,不能有所选择
    • 箭头函数中不能使用arguments。
      只要用arguments的场景,都要用rest语法代替。
    • 定义函数时:
      function fun(形参1,……, ...数组名){}
    • 将来调用时:
      1.前几个有名称的形参,分别接住自己对应位置的实参值。
      2.实参值列表中,前几个形参挑剩下的实参值,全都放入数组中。
      rest优点:
      1.数组名 是一个纯正的数组,数组加函数都可使用
      2.仅接受剩余的实参值,不和之前的形参抢参数值,可以有所选择。
      3.箭头函数也可以使用
  • 打散数组参数: 将一个数组,打散成多个元素值,分别传给函数。如果函数定义时,需要多个参数值,而实参值却是放在一个数组中传来的,就可以打散。

    • apply()可以打散数组为单个值,并传参。但pply()本职工作是替换this,顺便打散数组,但是很多打散数组的情况和this无关!
    • 调用函数时: fun(…数组),会将数组拆成多个元素传给fun()

总结:
1.定义函数时,形参列表中的…表示收集剩余参数
2.调用函数时,实参列表中的…表示打散

5.解构: destruct

将巨大的对象和数组中成员,单独提取出来使用。从前只要访问对象的成员,都必须加”对象.”前缀,太麻烦!只要从一个巨大的对象中提取成员单独使用时就用解构。

  • 解构3种使用方式:

    • 1.数组解构: 提取出数组中的个别元素,单独使用。 从前想使用数组中一个元素必须”数组名[下标]”,繁琐,而且下标是无意义的,让程序的可读性变差。
      下标对下标

      var [变量1, 变量2,...]=数组  
      // 变量1=数组中[0]位置的值  
      // 变量2=数组中[1]位置的值  
      ... ...
    • 2.对象解构: 提取出对象中的个别成员,单独使用。从前想使用对象中一个成员必须”对象.”前缀,繁琐。
      属性对属性

      var {属性1:变量1, 属性2:变量2, ... }=obj
      // 变量1=obj[属性1]的值
      // 变量2=obj[属性2]的值

      对象解构和打散操作:
      浅克隆一个对象: var obj2={…obj1}
      合并两个对象: var obj={…obj1, …obj2}
      等效于: var obj=Object.assign({},obj1,obj2)

    • 3.参数解构: 其实就是对象解构在传参时的应用。定义函数时,多个形参变量都不确定有没有,而且又要求实参值必须与形参变量对应时使用参数解构。

      • 使用方式 2步:
        1.定义函数时: 就要将所有形参变量都定义在一个对象结构中

          function fun({
        
          // 配对  变量
            属性1: 形参1
            属性2: 形参2
                ... : ...
          }){
            //函数体
          }

        2.调用函数时: 也必须传入一个对象,且对象中的属性名要和定义函数时,形参列表中的属性名保持一致!

          fun({
          //  配对    实参值
            属性1: 实参值1
            属性2: 实参值2
                ... : ...
          })

        结果: 形参1 通过解构获得 实参值1
        形参2 通过解构获得 实参值2

        问题: 如果调用函数时,没有提供指定属性名的实参值,是否会报错?
        答: 不会报错!因为解构是试图访问原对象中的成员。而js中访问对象成员如果不存在,不会报错,而是返回undefined。也就是说,如果调用函数时,缺少部分实参值,也不会报错,而是对应的形参变量获得undefined而已。

优点: 可随意省略任何一个实参值,都不会报错,还能保证其它几个实参值依然起作用。
  • 总结: 如果有形参值将来调用时不确定:
    • 如果只有最后一个形参值不确定: 默认值default
    • 如果多个形参值,内容不确定,个数也不确定,但是不要求实参值与形参变量之间的对应关系: 剩余参数 rest …arr
    • 如果多个形参值不确定,且要求实参值和形参变量必须对应: 参数解构

6.面向对象的简写:

  • 1.对{}的简写:

    • 所有的方法都可省略”:function”
      强调: 方法省略:function,和箭头函数原理完全不同。因为省略方法的:function,不影响this指向当前对象
    • 如果属性的值来自于一个变量,而变量名刚好和属性名一致,只需要写一个即可!
      var sname="Li Lei";
      var friends=["明明","红红","兔兔"]
      var lilei={
        sname:sname,   //sname 既当属性名,又当变量
        friends , 
        sage : 11
      }
    • 如果属性名需要动态生成: 属性名的位置不能拼接字符串或使用模板字符串。我们可以将js表达式放在一个[]中。
      var obj={
        [js表达式]: 属性值
      }

7. class

集中保存一种类型的构造函数和原型对象方法的程序结构。旧js中,构造函数和原型对象方法是分着写的,不符合封装的概念。将来只要创建一种类型,并反复创建多个子对象时,都要先创建class。

  • 创建class三步骤:
    • 1.用class{}包裹构造函数和原型对象方法
    • 2.构造函数名提升为类型名,写在class之后。
      所有构造函数去掉function,并统一更名为constructor
    • 3.所有原型对象方法,都可以去掉”类型名.prototype前缀”和”=function”

      强调: 直接定义在class中的属性,会自动成为每个子对象的自有属性,但是创建对象时,无法动态指定这种属性的属性值。所以,不建议使用。
      如果必须要在原型对象中添加共有属性值,只能在class外,用旧方法: 类型名.prototype.共有属性=值

使用class 和之前使用构造函数完全一样!

  • 两种class之间的继承
    当发现两种class之间存在部分相同的属性结构和方法定义时,都要定义一个父类型来集中管理两个class中相同部分的属性和方法定义。
    实现分为2步:
    • 定义一个父类型,集中保存两个类型相同部分的属性定义和方法定义
      1.父类型构造函数中包含子类型相同部分的属性定义
      2.父类型class中包含子类型相同部分的方法定义
    • 让子类型继承父类型
      1.class 子类型 extends 父类型{ … }。extends 类似于 Object.setPrototypeOf的作用
      2.子类型构造函数中,用supre(),调用父类型构造函数,和子类型构造函数一起

      强调: supre()必须写在子类型构造函数的开头,在所有子类型属性之前就要调用。因为: 这样可以保证,万一父类型和子类型刚巧有一个同名属性时,始终保证子类型的属性可以覆盖父类型的属性。而不允许父类型属性把子类型属性覆盖了。

8. Promise

专门解决异步函数顺序执行的问题。异步函数的执行在主程序之外,主程序不会等待异步任务完成才执行。所以无法保证执行顺序。使用传统的回调函数方式实现顺序执行,会形成回调地狱。

  • 创建new Promise()对象,包裹原异步函数的所有内容(原异步函数的内容不用变)。但是,new Promise()中需要保存的是原异步函数的所有代码段,所以,new Promise()中必须再嵌套一个function结构才能保存代码段。

     new Promise(function(){
        原异步函数代码段
      })
    • 因为在Promise对象内,需要开门,才能自动调用下一个任务函数,所以new Promise赠送function()了一个开门函数: new Promise(function(resolve){ ... })
    • 在原函数异步任务执行后,开门
      new Promise(function(resolve){
        //原异步任务代码段
        //异步函数最后执行的一句话后: 
        resolve()
      })
    • 最后,返回new Promise()对象到函数外部,用于和下一项任务对接。
      return new Promise(function(resolve){
        //原异步任务代码段
        //异步函数最后执行的一句话后: 
        resolve()
      })
  • 调用支持Promise的函数,并连接下一项任务
    前一个任务函数().then(下一项任务函数)

    • 调用前一项任务函数()做了两件事:
      1.创建一个房间
      2.让前一个异步任务在房间中执行

      因为调用前一个任务函数,会返回Promise对象而每个Promise对象都带一个钩子.then()可以接下一个任务函数。但是暂不执行下一项任务函数
      一旦前一个任务函数内调用了resolve()开门,则自动调用下一项任务函数。比如:

    function ming(){
      //1. 用
      return new Promise(function(resolve){
        console.log("明起跑...")
        setTimeout(function(){
          console.log("明到达终点!");
          //当自己的任务执行后
          resolve();//开门
        },6000)
      })
    }
    function hong(){ 
      return new Promise(function(resolve){
        console.log("红起跑...")
        setTimeout(function(){
          console.log("红到达终点!");
          //当自己的任务执行后
          resolve();//开门
        },6000)
      })
    }
    ming()//2件事: 
    // 1. 造房子  2. 让明在房子里起跑了
    //return new Promise(...)
    .then(hong) //将然挂在ming的房子之后
    //何时执行,必须等待明在房间中开门!
    //一旦明在房间中开门,.then会自动调用hong()
  • 错误处理: 一旦前一项任务中途出错,则下一项任务不再执行,而是进入错误处理流程。

    • 其实,new Promise()赠送给函数两个门: resolve和reject,
      return new Promise(function(resolve, reject){ ... })
    • 如果异步操作中出错了,就不要开resolve()这个正常的门,应该开reject()这个门,同时,将错误原因,通过reject(“错误提示”),将错误原因传递给后续错误处理程序
    • 只要在整个.then()链条的结尾添加一个.catch()就可接住前边任意一步任务中抛出的错误:
      .then(...).catch(function(rejectMsg){ ... })

      当之前任意一步函数中调用了reject(),都会停止整个流程,而是进入结尾的catch中执行函数。rejectMsg变量会接住reject(错误消息)参数中抛出的错误消息。比如:

    function ming(){
      return new Promise(function(resolve,reject){
        console.log(`明起跑...`)
        setTimeout(function(){
          if(Math.random()<0.6){
              console.log("明到达终点!");
              //当自己的任务执行后
              resolve();//开门,并把参数棒给下一个函数
          }else{
            //一旦出错,开reject这扇门,直通最后的catch(),并传出错误消息
            reject("吧唧,明摔倒了!")
          }
        },6000)
      })
    }
    function hong(){  
      return new Promise(function(resolve,reject){
        console.log(`红起跑...`)
        setTimeout(function(){
          if(Math.random()<0.6){
              console.log("红到达终点!");
              //当自己的任务执行后
              resolve();//开门,并把参数棒给下一个函数
          }else{
            //一旦出错,开reject这扇门,直通最后的catch(),并传出错误消息
            reject("吧唧,红摔倒了!")
          }
        },6000)
      })
     }
    ming()
    .then(hong)
    .catch(function(rejectMsg){
        console.log(rejectMsg);
        console.log("集体退赛!")
    })
  • 前一个任务向下一个任务传参: resolve()开门时,可以传入一个参数值。
    这个参数值,可以自动传给.then()中下一个函数的第一个参数。在下一个函数中可以使用。比如:

    function ming(){
        //1. 用
        return new Promise(function(resolve,reject){
            var bang="接力棒";
            console.log(`明拿着 ${bang} 起跑...`)
            setTimeout(function(){
              if(Math.random()<0.6){
                  console.log("明到达终点!");
                  //当自己的任务执行后
                  resolve(bang);//开门,并把参数棒给下一个函数
              }else{
                  //一旦出错,开reject这扇门,直通最后的catch(),并传出错误消息
                  reject("吧唧,明摔倒了!")
              }
            },6000)
        })
    }
    function hong(b1){
        console.log(`然拿着 ${b1} 起跑...`)
    } 
    ming().then(hong)

    结果: hong(b1)中的形参b1接住了ming()中resolve(bang)开门时传出的bang的参数值

    强调: resolve()这个函数,只能传入一个形参!如果有多个值需要传给下一个函数,可以将多个值放在一个数组或对象中传递:比如:

      function 前一个函数(){
        return new Promise(function(){
          ...
            var obj={sname: "Li Lei", sage:11}
            resolve(obj);
          ...
        })
      }
      ...
      function 下一个函数(obj){
        obj接住的就是: {sname: "Li Lei", sage:11}
      }
    

到这里Es5、Es6的重要内容都包括了,你是否有和之前不一样的理解呢?我在这里只是简单列举说明了一下重要内容,有什么不理解的可以再搜索一下详细资料,或者可以交流一下。在此奉上个人博客


文章作者: Love--金哥哥
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Love--金哥哥 !
评论
 上一篇
前端性能优化总结 前端性能优化总结
性能优化常用规则一、雅虎军规雅虎团队通过大量实践总结出以下7类35条前端优化规则。 内容 Make Fewer HTTP Requests:减少HTTP请求数 Reduce DNS Lookups:减少DNS查询 Avoid Redir
2020-04-17
下一篇 
一文搞懂Js中的面向对象 一文搞懂Js中的面向对象
前言面向对象是Js中永恒不变的话题,学好Js的面向对象编程能很大程度上提高代码的重用率和可维护性。这篇文章打算从基本概念到重点难点通过案例演示带你掌握面向对象编程。 基本概念 对象是程序中描述现实中一个具体事物的属性和功能的程序结构,程序中
2020-03-31
  目录