• JavaScript


    基本语法

    [0]. JavaScript原生提供的三个包装对象

    • Number
    • String
    • Boolean

    Number与parseInt

    parseInt:字符串转为整数,字符依次转换,遇到不能转为数字的字符,就不再进行下去,返回已经转好的部分
    Number:将任意类型的值转化成数值,字符串整体转换,若不可以被解析为数值,返回NaN

    /// parseInt/Number的返回值只有两种可能,一个十进制整数或NaN
    parseInt('8.8a') //8  Number('8.8a')  //NaN
    parseInt(''); //NaN   parseInt(null); //NaN  parseInt(undefined); //NaN   
    Number('');   //0     Number(null);   //0    Number(undefined);   //NaN
    

    Number要比parseInt严格很多。基本上只要有一个字符无法转成数值,整个字符串就会被转为NaN。

    • 第一步,调用对象自身的valueOf方法。如果返回原始类型的值,则直接对该值使用Number函数,不再进行后续步骤。
    • 第二步,如果valueOf方法返回的还是对象,则改为调用对象自身的toString方法。如果toString方法返回原始类型的值,则对该值使用Number函数,不再进行后续步骤。
    • 第三步,如果toString方法返回的是对象,就报错。
    var obj = {x: 1}; 
    Number(obj);  // NaN
    // 等同于
    if (typeof obj.valueOf() === 'object') {
      Number(obj.toString());
    }
    else {
      Number(obj.valueOf());
    }
    
    Number({
      valueOf: function () {
        return 2;
      },
      toString: function () {
        return 3;
      }
    });  // 2
    

    表示valueOf方法先于toString方法执行。
    注:一元运算符(+)同Number()方法功能一样。

    String

    将任意类型的值转化成字符串。

    String(true) // "true"
    String(undefined) // "undefined"
    String(null) // "null"
    

    与Number方法基本相同,只是互换了valueOf方法和toString方法的执行顺序。

    • 先调用对象自身的toString方法。如果返回原始类型的值,则对该值使用String函数,不再进行以下步骤。
    • 如果toString方法返回的是对象,再调用原对象的valueOf方法。如果valueOf方法返回原始类型的值,则对该值使用String函数,不再进行以下步骤。
    • 如果valueOf方法返回的也是对象,就报错。
    String({
      valueOf: function () {
        return 2;
      },
      toString: function () {
        return 3;
      }
    }); // "3"
    

    表示toString方法先于valueOf方法执行。

    字符串方法总结

    split() 方法用于把一个字符串分割成字符串数组。
    slice方法:slice(start,end) end是可选参数。1个参数:从开始位置截取到结尾 ,2个参数:从开始位置截取到结束位置(不包括结束位置本身,结束位置还可以是负数)
    push方法 :可向数组的末尾添加一个或多个元素,并返回新的长度。
    pop方法:用于弹出数组最后一个元素,返回该元素的值
    shift:将数组第一个元素从其中删除,并且返回第一个元素的值
    unshift: 在数组开头添加元素,返回该元素值
    join:将数组元素通过指定的字符连接成字符串。参数若是为无,默认为','
    reverse:用于将数组元素颠倒顺序。无参数,操作的是数组本身,会改变原数组
    sort(fn):将数组元素排序,会改变原数组的。
        参数:fn(a,b):比较函数 ;无参数的时候按照字母表升顺排序;参数a,b分别表示数组中的任意两个元素,若 return > 0 则表示 则b前a后,若 return < 0 则表示 a前b后。
    splice:用于截取数据,插入数据,删除数据。操作的是数据本身。
        参数:1.开始位置 2.截断的个数 3.插入的数据
    indexOf:返回在该数组中第一个找到的元素位置,若不存在则返回 -1
    filter:筛选函数,不改变原数组。
    -----------------------------------------------------------------------------
    传统上,JavaScript只有indexOf方法,可以用来确定一个字符串是否包含在另一个字符串中。ES6又提供了三种新方法。
    includes():返回布尔值,表示是否找到了参数字符串。
    startsWith():返回布尔值,表示参数字符串是否在源字符串的头部。
    endsWith():返回布尔值,表示参数字符串是否在源字符串的尾部。
    repeat():用于返回一个新字符串,表示将原字符串重复n次。
            eg:'x'.repeat(3)  //"xxx"
            参数如果是小数,会被取整。
            参数如果是负数或Infinity,会报错。
            如果参数是0到-1之间的小数,则等同于0。
            如果repeat的参数是字符串,则会先转换成数字。
            参数NaN等同于0。
    padStart(),padEnd():如果某个字符串不够指定长度,会在头部或尾部补全。padStart用于头部补全,padEnd用于尾部补全。
            padStart和padEnd一共接受两个参数,第一个参数用来指定字符串的最小长度,第二个参数是用来补全的字符串。
            如果用来补全的字符串与原字符串,两者的长度之和超过了指定的最小长度,则会截去超出位数的补全字符串。
            如果省略第二个参数,则会用空格补全长度。
    		
    模板字符串:ES6引入了模板字符串解决这个问题。
            eg:
             $('#result').append(`
            There are <b>${basket.count}</b> items
             in your basket, <em>${basket.onSale}</em>
             are on sale!
             `);     
    模板字符串(template string)是增强版的字符串,用反引号(`)标识。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量。
    模板字符串中嵌入变量,需要将变量名写在${}之中。
    

    Boolean

    将任意类型的值转为布尔值。

    Boolean(false) // false
    Boolean({}) // true
    Boolean([]) // true
    Boolean(new Boolean(false)) // true
    

    [1]. JavaScript中三种方法确定值的类型:

    • typeof运算符
    • instanceof运算符
    • Object.prototype.toString方法
    typeof(null) --> "object"
    typeof(undefined) --> "undefined"
    typeof(NaN) --> "Number"

    非数值类型转换为数值类型

     6 + null --> 6   // Number(null); 
     6 + undefined  --> NaN  // Number(undefined); 

    instanceof

    表示对象是否为某个构造函数的实例。但,只能用于对象,不适用原始类型的值。

    检查右边构造函数的原型对象(prototype),是否在左边对象的原型链上。

    v instanceof V
    // 等同于
    V.prototype.isPrototypeOf(v)
    

    利用该运算符,可以巧妙解决忘记new命令的问题

    function GG (g1, g2) {
      if (this instanceof GG) {
        this._g1 = g1;
        this._g2 = g2;
        console.log("111111");
      } else {
        console.log("222222");
        return new GG(foo, bar);
      }
    }
    
    var GG2 = new GG(1,2);
    // 111111
    
    var GG1 = GG(1,2);
    // 222222
    // 111111
    

    字节序

    分为小端(little endian)和大端(big endian):

    • 大端字节序:高位字节在前,低位字节在后,这是人类读写数值的方法
    • 小端字节序:低位字节在前,高位字节在后

    下面是判断计算机字节序的方法

    // true:小端,false:大端
    var littleEndian = (function() {
      var buffer = new ArrayBuffer(2);
      new DataView(buffer).setInt16(0, 256, true);  // 小端字节序写入
      return (new Int16Array(buffer))[0] === 256;
    })();
    

    通常个人PC都是小端字节序,因为计算机先处理低位字节,效率比较高。

    以32位整数求值为例

    /* 大端字节序 */
    i = (data[3]<<0) | (data[2]<<8) | (data[1]<<16) | (data[0]<<24);
    /* 小端字节序 */
    i = (data[0]<<0) | (data[1]<<8) | (data[2]<<16) | (data[3]<<24);
    

    关于字节序,参见:理解字节序 - 阮一峰;  

    [2]. Infinity  

    Infinity大于一切数值(除了NaN),-Infinity小于一切数值(除了NaN)
    Infinity与NaN比较,总是返回false(任何值(包括NaN本身)与NaN比较,返回的都是false)

    0 * Infinity // NaN
    Infinity - Infinity // NaN
    Infinity / Infinity // NaN
    

    Infinity与null运算,等同于与0运算

    Infinity与undefined运算,总是等于NaN

    [3]. 布尔类型  

    以下6个的布尔值为false,其余均为true 

    undefined
    null
    false
    0
    NaN
    ""或''(空字符串)
    

    空数组[]和空对象{}也为true。 

    ! 与 !!:强制转换为布尔型

    • !:类型判断,逻辑取反,可将null、undefined和空串取反为false
    • !!:类型判断,双重逻辑取反,判断变量 !!a 等效于 a!=null&&typeof(a)!=undefined&&a!=''&&a!="" 

    [4]. 字符集

    JavaScript使用Unicode字符集。
    每个字符在JavaScript内部都是以16位(即2个字节)的UTF-16格式储存。
    也就是说,JavaScript的单位字符长度固定为16位长度,即2个字节。
    JavaScript无法识别四字节的字符,也就是说,JavaScript返回的字符串长度可能是不正确的。

    判断一个字符由两个字节还是由四个字节组成的最简单方法:

    function is4Bytes(c) {
      return c.codePointAt(0) > 0xFFFF;
    }
    

    [5]. Base64编码

    • 将文本中不可打印的特殊符号转成可打印的字符;
    • 以文本格式传递二进制数据;

    非ASCII码字符转的Base64编码,需要转码环节

    function b64Encode(str) {
      return btoa(encodeURIComponent(str));
    }
    function b64Decode(str) {
      return decodeURIComponent(atob(str));
    }
    
    b64Encode('你好') // "JUU0JUJEJUEwJUU1JUE1JUJE"
    b64Decode('JUU0JUJEJUEwJUU1JUE1JUJE') // "你好"
    

    借此,了解下 js中 encodeURIencodeURIComponent

    • 把字符串作为 URI 进行编码
    • 方法不会对 ASCII 字母和数字进行编码,也不会对这些 ASCII 标点符号进行编码: - _ . ! ~ * ' ( ) 。

    主要的区别如下

    • encodeURI(URIstr): 对在 URI 中具有特殊含义的 ASCII 标点符号,不会进行转义的:;/?:@&=+$,#
    • encodeURIComponent(URIstr):对在 URI 中具有特殊含义的 ASCII 标点符号,也会进行转义的:;/?:@&=+$,#

    [6]. isNaN() 与 isFinite()  

    isNaN():判断一个值是否为NaN,利用NaN是唯一不等于自身的值,用于isNaN()判断
    isFinite():表示某个值是否为正常的数值,Infinity、-Infinity、NaN和undefined返回false,其余均返回true

    [7]. 数组

    数组的length属性的值 = 最大的数字键 + 1
    将数组的键分别设为字符串或小数(为数组添加属性),结果都不影响length属性。

    • push和pop:“后进先出”的栈结构(stack)  
    • push和shift:“先进先出”的队列结构(queue)

    二进制数组

    允许以二进制数组操作二进制数据,支持浏览器与显卡之间以二进制的形式进行通信。

    • ArrayBuffer对象:代表原始的二进制数据。表示内存之中的一段二进制数据,支持以数组的方法操作内存
    • TypedArray对象:代表确定类型的二进制数据。用来生成内存的视图
    • DataView对象:代表不确定类型的二进制数据。用来生成内存的视图,支持自定义格式和字节序

    对ArrayBuffer的读写均需通过视图实现。

    TypedArray视图和DataView视图,两者的区别主要是字节序,前者的数组成员都是同一个数据类型,后者的数组成员可以是不同的数据类型。

    二进制数组应用

    • Ajax
    • Canvas
    • WebSocket
    • Fetch API
    • File API

    [8]. 闭包

    JavaScript语言特有的“链式作用域”结构(chain scope),子对象会一级一级地向上寻找所有父对象的变量。
    也就是说,父对象的所有变量,对子对象都是可见的,反之则不成立。
    闭包就是函数,能够读取其他函数内部变量的函数。
    由于在JavaScript语言中,只有函数内部的子函数才能读取内部变量,因此可以把闭包简单理解成“定义在一个函数内部的函数”。
    闭包最大的特点:“记住”诞生的环境
    闭包的最大用处有两个:

    • 可以读取函数内部的变量
    • 让变量始终保持在内存中,即闭包使诞生环境一直存在(闭包可以看作是函数内部作用域的一个接口)

    本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁
    实际中,常用于封装对象的私有属性和私有方法

    [9]. eval命令

    将字符串当作语句执行:

    • 直接使用:eval(expression),当前局部作用域
    • 间接调用:除eval(expression)外,全局作用域
    var a = 1;
    function f() {
      var a = 2;
      var foo = eval;
      foo("console.log(a)");
    }
    f() // 1
    
    var a = 1;
    function f() {
      var a = 2;
      eval("console.log(a)");
    }
    f() // 2
    

    该命令JavaScript引擎不优化,运行速度较慢。

    通常情况下,eval最常见的场景是解析Json数据字符串。
    最正确的做法应该是:使用浏览器提供的JSON.parse方法

    [10.1]. 对象

    对象转换成原始类型的值:obj.valueOf().toString()

    • 对象的valueOf方法总是返回对象自身
    • 对象的toString方法默认返回[object Object]

    JavaScript语言的继承是通过“原型对象”(prototype),而不是“类”(class)。

    至于原因,参见:Javascript继承机制的设计思想

    注:在出现对象或函数的地方,可以用类替换,或许更容易理解。

    new

    C#/Java/C++中的new用法

    类名/var obj = new 类名();
    

    而JS中的new用法,略有不同,是其简化设计版

    var obj = new 构造函数名();
    

    使用new命令时,其后面的构造函数依次执行:

    • 创建一个空对象,作为将要返回的对象实例
    • 将这个空对象的原型,指向构造函数的prototype属性
    • 将这个空对象赋值给函数内部的this关键字
    • 开始执行构造函数内部的代码

    特别地,注意构造函数体中有return语句的情况。

    new命令简化的内部流程:

    // /* 构造函数,构造函数参数 */
    function _new( constructor,  params) {
      // 将 arguments 对象转为数组
      var args = [].slice.call(arguments);
      // 取出构造函数
      var constructor = args.shift();
      // 创建一个空对象,继承构造函数的 prototype 属性
      var context = Object.create(constructor.prototype);
      // 执行构造函数
      var result = constructor.apply(context, args);
      // 如果返回结果是对象,就直接返回,否则返回 context 对象
      return (typeof result === 'object' && result != null) ? result : context;
    }
    

    toString()

    Object.prototype.toString方法返回对象的类型字符串,可以判断一个值的类型:Object.prototype.toString.call(value)

    var type = function (o){
      var s = Object.prototype.toString.call(o);
      return s.match(/[object (.*?)]/)[1].toLowerCase();
    };
    

    这是比 typeof 运算符更准确的类型判断函数。

    this

    this是属性或方法“当前”所在的对象。但是,this的指向是动态的。

    • 全局环境:this指向顶层对象window(this === window;  //true)
    • 构造函数:指向实例对象
    • 对象的方法:指向方法运行时所在的对象
    var obj ={
      foo: function () {
        console.log(this);
      }
    };
    
    // 表示调用foo方法,this指向对象obj
    obj.foo()
    // 表示属性foo对应的值,this指向顶层对象
    obj.foo
    

    直白理解,JS引擎内部,obj和obj.foo储存在两个内存地址:地址一和地址二。obj.foo()这样调用,是从地址一调用地址二,因此地址二的运行环境是地址一,this指向obj。但是,obj.foo表示直接取出地址二执行,因此运行环境就是全局环境,this指向全局环境。  

    注意

    • 避免多层this
    • 避免在数组处理方法中使用this
    • 避免在回调函数中用this

    对于多层this,可以将外层的this先赋值给临时变量tmp,内层函数调用this的地方用tmp替换。

    var o = {
      v: 'hello',
      p: [ 'a1', 'a2' ],
      f: function f() {
        var that = this;
        this.p.forEach(function (item) {
          console.log(that.v+' '+item);
        });
      }
    }
    

    this绑定

    JS提供 callapplybind 三种方法,实现动态切换/固定this的指向。

    (1)call

    指定函数内部this的指向。默认参数为空,则指向全局对象。

    func.call(thisValue, arg1, arg2, ...)
    

    (2)apply

    同call方法,唯一的区别就是,它接收一个数组作为函数执行时的参数

    func.apply(thisValue, [arg1, arg2, ...])
    
    • 将数组的空元素变为undefined
    • 转换类数组对象为真正的数组
    • 绑定回调函数的对象 

    call和apply方法绑定函数执行时所在的对象,还会立即执行函数。 

    (3)bind

    将函数体内的this绑定到某个对象,然后返回一个新函数,而且(每一次绑定均返回一个新的函数)

    :涉及到this时(特别是有些库方法内部有this,不易察觉),在将函数赋值给其他变量时,务必注意this的绑定。

    对this的详细理解,请参见:this关键字

    prototype

    JS规定

    每一个构造函数都有一个prototype属性,该属性指向另一个特殊的对象。这个对象的所有属性和方法,都会被构造函数的实例继承。

    JS继承机制的设计思想:原型对象的所有属性和方法都能被实例对象共享。也就是说,如果属性和方法定义在原型上,那么所有实例对象均可共享。

    • 数据共享,节省内存
    • 体现实例对象之间的联系

    可以理解成接口或者是类的静态变量。实例对象可以视作是从原型对象衍生出来的子对象。

    JavaScript规定:

    • 每个函数都有一个prototype属性,指向一个对象
    • 所有对象都有自己的原型对象prototype

    原型链

    prototype chain:对象到原型,再到原型的原型,直至null。

    Object.prototype是所有对象的原型,而Object.prototype的原型是null

    Object.getPrototypeOf(Object.prototype);  // null
    

    prototype对象的constructor属性,默认指向prototype对象所在的构造函数 。

    function F() {}
    var f = new F();
    f.constructor === F // true
    f.constructor === f.prototype.constructor // true
    

    利用该属性,可以判定某个实例对象,是哪个构造函数产生的。

    f.constructor.name; // "F"
    

    也可以由一个实例对象新建另一个实例,利用f.constructor间接调用构造函数

    F.prototype.createCopy = function () {
      return new this.constructor();
    };
    

    constructor属性表示原型对象与构造函数之间的关联关系,若修改原型对象,需同时修改constructor属性的指向。

    // 好的写法:将constructor属性重新指向原来的构造函数
    F.prototype = {
      constructor: F,
      method1: function (...) { ... },
      // ...
    };
    
    // 更好的写法:只在原型对象上添加方法
    F.prototype.method1 = function (...) { ... };
    

    Object

    获取原型对象的标准方法:Object.getPrototypeOf

    // 实例对象f的原型是 F.prototype
    Object.getPrototypeOf(f);  // F.prototype
    // 空对象的原型是 Object.prototype
    Object.getPrototypeOf({});  // Object.prototype
    // Object.prototype 的原型是 null
    Object.getPrototypeOf(Object.prototype);  // null
    // 函数的原型是 Function.prototype
    function f() {}
    Object.getPrototypeOf(f);  // Function.prototype
    

    实例对象的__proto__属性,返回该对象的原型,等同于getPrototypeOf方法。

    生成实例对象的常用方法:Object.create

    实质是新建一个空的构造函数F,然后让F.prototype属性指向参数对象obj,最后返回一个F的实例,从而实现让该实例继承obj的属性。

    Object.create = function (obj) {
        function F() {}
        F.prototype = obj;
        return new F();
      };
    

    创建一个空对象

    var obj1 = Object.create({});
    var obj2 = Object.create(Object.prototype);
    var obj3 = new Object();
    

    若想生成一个纯粹的空对象,即不继承任何属性

    var obj = Object.create(null);
    

    获取所有属性键名:Object.getOwnPropertyNames

    只返回对象本身的,不包含继承的,而且不管是否可以遍历(与Object.keys的区别)。

    对应的,hasOwnProperty方法用于判定是否存在于对象本身(与in的区别),该方法是JS中唯一一个处理对象属性时,不会遍历原型链的方法。

    获得对象的所有属性(不管是自身的还是继承的,也不管是否可枚举)

    function allPropertyNames(obj) {
      var props = {};
      while(obj) {
        Object.getOwnPropertyNames(obj).forEach(function(p) {
          props[p] = true;
        });
        obj = Object.getPrototypeOf(obj);
      }
      return Object.getOwnPropertyNames(props);
    }
    

    对象的拷贝

    确保拷贝后的对象:

    • 与原对象具有同样的原型
    • 与原对象具有同样的实例属性
    function copyObject(orig) {
      // 保证原型相同
      var copy = Object.create(Object.getPrototypeOf(orig));
      // 保证实例属性相同 
      copyOwnPropertiesFrom(copy, orig);
      return copy;
    }
     
    function copyOwnPropertiesFrom(target, source) {
      Object
        .getOwnPropertyNames(source)
        .forEach(function (propKey) {
          var desc = Object.getOwnPropertyDescriptor(source, propKey);
          Object.defineProperty(target, propKey, desc);
        });
      return target;
    }
    

    采用ES2017引入的标准的Object.getOwnPropertyDescriptors方法,简化为

    function copyObject(orig) {
      return Object.create(
          Object.getPrototypeOf(orig),
          Object.getOwnPropertyDescriptors(orig)
      );
    }
    

    关于拷贝,可参见:Javascript面向对象编程(三):非构造函数的继承 中 深拷贝 部分,此处仅引用其代码

    function deepCopy(p, c) {
      var c = c || {};
      for (var i in p) {
        if (typeof p[i] === 'object') {
          c[i] = (p[i].constructor === Array) ? [] : {};
          deepCopy(p[i], c[i]);
        } else {
           c[i] = p[i];
        }
      }
      return c;
    }
    

    其实,此代码仅满足了上述的第2个条件。

    属性描述对象

    attributes object,JS提供的内部数据结构,用来描述对象的属性,控制对象的行为

    • value
    • writable
    • enumerable
    • configurable
    • get
    • set

    如果一个属性的enumerable为false下面三个操作不会取到该属性

    • for..in循环
    • Object.keys方法
    • JSON.stringify方法

    存取器:get 与 set ,有2种定义形式(第2种更常用)

    // 第一种
    var obj = Object.defineProperty({}, 'p', {
      get: function () {
        return 'getter';
      },
      set: function (value) {
        console.log('setter: ' + value);
      }
    });
    
    // 第二种,常用
    var obj = {
      get p() {
        return 'getter';
      },
      set p(value) {
        console.log('setter: ' + value);
      }
    };
    

    拷贝:将一个对象的所有属性,拷贝到另一个对象

    var copyF= function (to, from) {
      for (var property in from) {
        if (!from.hasOwnProperty(property))
          continue;
        
        Object.defineProperty(
          to, property,
          Object.getOwnPropertyDescriptor(from, property)
        );
      }
    
      return to;
    }   

    行为控制:控制读写状态,防止对象被改变

    • Object.preventExtensions:无法添加新属性
    • Object.seal:既无法添加新属性,也无法删除旧属性
    • Object.freeze:无法添加新属性、无法删除旧属性、也无法改变属性的值,近似常量

    从上至下,控制性越强。Object.seal实质是把属性描述对象的configurable属性设为false。

    若想彻底锁定对象,需要锁定对象的原型

    var obj = new Object();
    Object.preventExtensions(obj);
    var proto = Object.getPrototypeOf(obj);
    Object.preventExtensions(proto);
    

    这样就避免通过改变原型对象,间接改变对象属性。

    [10.2]. 继承

    子构造函数(子类)继承父构造函数(父类)

    • 子类继承父类的实例:在子类的构造函数中,调用父类的构造函数
    • 子类继承父类原型:子类的原型指向父类的原型
    function Son() {
      Father.call(this); 
    }
    
    Son.prototype = Object.create(Father.prototype);
    Son.prototype.constructor = Son;
    

    关于继承的问题,参见:Javascript面向对象编程(二):构造函数的继承

    另一种实现形式,异曲同工

    function extend(Son,Father){
        var Tmp = function(){};
        Tmp.prototype = Father.prototype;
        
        var tmpObj = new Tmp( );
        
        Son.prototype = tmpObj;
        Son.prototype.constructor = Son;  //必写,否则孩子的构造函数指向不对
        Son.super = Father.prototype;  //可写,提供孩子访问父亲的方法
    }
    

    该方法也是 YUI 库实现继承的方法,图示原型链

    特别是在:原型继承,对原型链的图示,有助于对prototype的理解。

    JS不提供多重继承功能(不允许一个对象同时继承多个对象),但可以间接实现

    function M1() {
      this.hello = 'hello';
    }
    function M2() {
      this.world = 'world';
    }
    
    function S() {
      M1.call(this);
      M2.call(this);
    }
    
    // 继承 M1
    S.prototype = Object.create(M1.prototype);
    // 继承链上加入 M2
    Object.assign(S.prototype, M2.prototype);
    // 指定构造函数
    S.prototype.constructor = S;
    

    该实现模式称为 Mixin(混入)。

    封装私有变量

    • 普通构造函数形式
    • 立即执行函数(IIFE):不暴露私有成员
    • 模块的放大模式:支持模块拆分和继承
    • 宽放大模式:与放大模式相比,宽放大模式的“立即执行函数”的参数可以是空对象
    // 放大模式
    var module2 = (function (mod){
     mod.m3 = function () {
      //...
     };
     return mod;
    })(module1);
    
    // 宽放大模式
    var module2 = ( function (mod){
     //...
     return mod;
    })(window.module1 || {});
    

    在未出现class关键字前,可采用如下方式模拟类

    var Animal = {
      name: "Animal",
     createNew: function(){
      var animal = {};
      animal.sleep = function(){ alert(name + "sleep"); };
      return animal;
     }
    };
    
    var Cat = {
     createNew: function(_name){
      var cat = Animal.createNew();
      cat.name = _name;
      cat.makeSound = function(){ alert(_name + "miao"); };
      return cat;
     }
    };
    

    参见:Javascript定义类(class)的三种方法

    在ES6中,引入了class关键字,更接近Java/C#的用法,极大地简化了原型链代码。

    [11]. == 与 ===

    • ==:相等运算符,比较两个值是否相等
    • ===:严格相等运算符,比较它们是否为“同一个值”

    如果两个值不是同一类型,===直接返回false,而==会将它们转换成同一个类型,再用===进行比较。
    对于两个对象的比较,===比较的是地址,而大于或小于运算符比较的是值。

    // undefined和null与自身严格相等
    undefined ===/== undefined  //true
    null ===/== null            //true
    NaN ===/== NaN              //false
    undefined == null           //true
    

    不要使用相等运算符(==),最好只使用严格相等运算符(===)。

    [12]. 位运算

    所有的位运算都只对整数有效。
    位否运算:一个数与自身的取反值相加,等于-1。

    位否运算遇到小数时,会将小数部分舍去,只保留整数部分。对一个小数连续进行两次二进制否运算,能达到取整效果。

    ~~ -1.9999 // -1
    

    异或运算也可以实现取整运算

    -8.9 ^ 0  //-8
    

    但是使用二进制否运算取整,是所有取整方法中最快的一种。
    位或运算:将任意数值转为32位带符号整数

    function toInt32(x) {
      return x | 0;
    }
    toInt32(Math.pow(2, 32) + 1) // 1
    toInt32(Math.pow(2, 32) - 1) // -1
    

    [13]. switch…case

    通常是 case...break 的形式

    function doAction(action) {
      switch (action) {
        case 'hack':
          return 'hackAction';
          break;
        case 'slash':
          return 'slashAction';
          break;
        case 'run':
          return 'runAction';
          break;
        default:
          throw new Error('Invalid action.');
      }
    }
    

    建议写成对象结构,避免代码冗长

    function doAction(action) {
      var actions = {
        'hack': function () {
          return 'hackAction';
        },
        'slash': function () {
          return 'slashAction';
        },
        'run': function () {
          return 'runAction';
        }
      };
    
      if (typeof actions[action] !== 'function') {
        throw new Error('Invalid action.');
      }
      return actions[action]();
    }
    

    [14]. JSON
    stringify()与parse()

    • JSON.stringify:将一个值转为JSON字符串
    • JSON.parse:将JSON字符串转换成对应的值

    toJSON()
    若参数对象有自定义的toJSON方法,那么JSON.stringify会使用此方法的返回值作为参数,而忽略原对象的其他属性。

    var user = {
      firstName: '三',
      lastName: '张',
    
      get fullName(){
        return this.lastName + this.firstName;
      },
    
      toJSON: function () {
        return {
          name: this.lastName + this.firstName
        };
      }
    };
    
    
    JSON.stringify(user); // "{"name":"张三"}"
    /// 等效于
    JSON.stringify(user.toJSON());
    

    toJSON()的一个应用是,将正则对象自动转为字符串。
    因为JSON.stringify默认不能转换正则对象,但设置了toJSON()后,就可以转换正则对象

    var obj = {
      reg: /foo/
    };
    
    // 不设置 toJSON 方法时
    JSON.stringify(obj) // "{"reg":{}}"
    // 设置 toJSON 方法时
    RegExp.prototype.toJSON = RegExp.prototype.toString;
    JSON.stringify(/foo/) // ""/foo/"" 
    

    [15]. 正则表达式

    正则表达式中,需要反斜杠转义的一共有12个字符:^、.、[、$、(、)、|、*、+、?、{和\。

    • [Ss]:指代一切字符
    • w:匹配任意的字母、数字和下划线,相当于[A-Za-z0-9_]

    replace

    如果不加g修饰符,仅替换第一个匹配成功的值,否则替换所有匹配成功的值。

    'aaa'.replace('a', 'b') // "baa"
    'aaa'.replace(/a/, 'b') // "baa"
    'aaa'.replace(/a/g, 'b') // "bbb"
    

    replace方法的一个应用:去除空格

    // str不变
    var str = '  #id div.class  ';
    var ss = str.replace(/^s+|s+$/g, '');
    或
    var ss = str.trim(); 
    

    量词符
    用来设定某个模式出现的次数。

    • ?  问号表示某个模式出现0次或1次,等同于{0,1}
    • *  星号表示某个模式出现0次或多次,等同于{0,}
    • + 加号表示某个模式出现1次或多次,等同于{1,}

    默认最大可能匹配:贪婪模式
    若改为非贪婪模式,在 * 或 + 的后面加上 ? 即可。
    修饰符

    g 表示全局匹配(global),主要用于搜索和替换
    i 表示忽略大小写(ignorecase)
    m 表示多行模式(multiline),使^和$会识别换行符(
    )
    s 使得.可以匹配任意单个字符,包括换行符
    或
     (dotAll模式)
    y “粘连”(sticky)修饰符,g 的加强版
    u 用于正确处理大于uFFFF的 Unicode 字符(Unicode 模式)
    

    断言

    • 先行断言:lookahead,/x(?=y)/
    • 先行否定断言:negative lookahead,/x(!=y)/
    • 后行断言:lookbehind,/(?<=y)x/
    • 后行否定断言:negative lookbehind,/(?<!y)x/

    关于正则表达式,详情请见:http://javascript.ruanyifeng.com/stdlib/regexp.html

    具名组匹配

    利用exec提取()匹配的结果,同时为每一个结果预先指定名字。

    • 定义:?<name>
    • 使用:res.groups.name

    也可以在其他地方引用:$<name>

    若是在某个正则表达式内部引用:k<name>

    [16]. 计时方法

    计时

    获取某段代码的执行耗时

    console.time('计时器名');
    xxx...xxx
    console.timeEnd('计时器名');
    
    // 计时器名: xxx.xxxxms
    

    定时器

    • setTimeout()
    • setInterval()

    运行机制:将指定的代码移出本轮事件循环,等到下一轮事件循环,再检查是否到了指定时间

    • 如果不到,继续等待
    • 如果到了,且本轮事件循环的所有同步任务执行完,再执行对应的代码(回调函数)

    为确保两次执行之间有固定的间隔,不建议用setInterval,而是每次执行结束后,使用setTimeout指定下一次执行的具体时间

    var timer = setTimeout(function f() {
      // ...
      timer = setTimeout(f, 2000);
    }, 2000);
    

    上面代码可以确保,下一次执行总是在本次执行结束之后的2000毫秒开始。

    有关setTimeout()的原理,请参见:https://www.jianshu.com/p/3e482748369d?from=groupmessage

    防抖动:debounce()
    假定两次Ajax通信的间隔不得小于2500毫秒,实际中应该

    $('textarea').on('keydown', debounce(ajaxAction, 2500));
    
    function debounce(fn, delay){
      var timer = null; // 声明计时器
      return function() {
        var context = this;
        var args = arguments;
        clearTimeout(timer);
        timer = setTimeout(function () {
          fn.apply(context, args);
        }, delay);
      };
    }
    

    若是如下格式,会存在连续点击触发keydown事件、频繁调用回调函数的问题。

    $('textarea').on('keydown', ajaxAction);
    

    setTimeout(fun, 0)
    尽可能早地执行fun,但是并不能保证立刻就执行fun。

    • 调整任务执行顺序
    • 拆分计算量大耗时长的任务:代码块高亮、改变网页背景色  

    关于定时操作,可参见:定时器 - 阮一峰

    [17]. 异步操作

    JS异步操作的几种模式

    • 回调函数:最基本
    • 事件监听:事件驱动
    • 发布/订阅:观察者模式

    其中,发布订阅模式优于事件监听模式,具体参考:异步操作概述 - 阮一峰; 

    Promise
    Promise对象是JavaScript的异步操作解决方案,为异步操作提供统一接口

    • 代理作用(proxy),充当异步操作与回调函数之间的中介桥梁
    • 使得异步流程可以写成同步流程

    设计思想:所有异步任务都返回一个Promise实例。

    • 预加载图片
    • Ajax操作

    Promise支持:串行执行异步任务 和 并行执行异步任务。

    关于Promise,可参见:Promise对象 - 阮一峰Promise - 廖雪峰

    [18]. 有限状态机

    Finite-state machine

    • 状态(state)总数是有限的
    • 任一时刻,只处在一种状态之中
    • 某种条件下,会从一种状态转变(transition)到另一种状态

    结合异步操作,与对象的状态改变挂钩,当异步操作结束时,发生相应的状态改变,由此再触发其他操作。

    可以利用有限状态机的函数库:Javascript Finite State Machine

    var fsm = StateMachine.create();
    
    • 允许为每个事件指定两个回调函数:onBeforeEventA,onAfterEventA
    • 允许为每个状态指定两个回调函数:onLeaveState1,onEnterState1

    若事件EventA使得由状态State1变为State2,则调用顺序为

    onBeforeEventA → onLeaveState1 → onEnterState2 → onAfterEventA

    参见:JS与有限状态机Javascript State Machine

    DOM模型

    JS执行速度远高于DOM。

    详情请参见:JS - DOM - sqh; 

    Ajax

    详情请参见:JS - Ajax - sqh;  

    参考

    JavaScript 标准参考教程(alpha)- 阮一峰ECMAScript 6  - 阮一峰

    JavaScript教程 - 廖雪峰

  • 相关阅读:
    异步加载技术实现瀑布流效果
    点击向下展开的下拉菜单特效
    几个个实用的PHP代码片段【自己备份】
    cache和buffer区别探讨
    windows 文本文件放到linux下使用
    制作rpm包
    mariadb在线热备份做主从
    检查目录下备份文件的脚本
    different between method and function
    mysql忘记root密码解决
  • 原文地址:https://www.cnblogs.com/wjcx-sqh/p/9179998.html
Copyright © 2020-2023  润新知