美文网首页
《你不知道的JavaScript》小结2

《你不知道的JavaScript》小结2

作者: Mr君 | 来源:发表于2019-05-15 11:14 被阅读0次
    var anotherObject = {
      a:2
     };
    
    var myObject = Object.create( anotherObject );
    anotherObject.a; // 2
    myObject.a; // 2
    
    for (var k in myObject) {
      console.log("found: " + k);
    }
    // found: a
    
    ("a" in myObject); // true
    
    anotherObject.hasOwnProperty( "a" ); // true
    myObject.hasOwnProperty( "a" ); // false
    myObject.a++; 
    anotherObject.a; // 2
    myObject.a; // 3
    myObject.hasOwnProperty( "a" ); // true
    

    原因: ++ 操作相当于 myObject.a = myObject.a + 1 。因此 ++ 操作首先会通过 [[Prototype]]查找属性 a 并从 anotherObject.a 获取当前属性值 2,然后给这个值加 1,接着用[[Put]]将值 3 赋给 myObject 中新建的屏蔽属性 a

    function Foo() { /* .. */ }
    Foo.prototype = { /* .. */ }; // 创建一个新原型对象
    var a1 = new Foo();
    a1.constructor === Foo; // false!
    a1.constructor === Object; // true!
    

    原因: a1 并没有 constructor 属性,所以它会委托[[Prototype]] 链上的 Foo.prototype。但是这个对象也没有 constructor 属性,所以它会继续委托,这次会委托给委托链顶端的 Object.prototype 。这个对象有.constructor 属性,指向内置的 Object(..) 函数。

    1. 关于instanceof
      instanceof操作符的左操作数是一个普通的对象,右操作数是一个函数。a instanceof Foo; // trueinstanceof回答的问题是:在 a的整条[[Prototype]]链中是否有指向 Foo.prototype 的对象?
      这个方法只能处理对象( a )和函数(带 .prototype 引用的 Foo )之间的关系。如果你想判断两个对象(比如 ab )之间是否通过 [[Prototype]]链关联,只用 instanceof无法实现。参考isPrototypeOf解决上述问题。
    // 用来判断 o1 是否关联到(委托)o2 的辅助函数
    function isRelatedTo(o1, o2) {
      function F(){}
      F.prototype = o2;
      return o1 instanceof F;
    }
    
    var a = {};
    var b = Object.create( a );
    isRelatedTo( b, a ); // true
    
    1. 关于数组
      如果字符串键值能够被强制类型转换为十进制数字的话,它就会被当作数字索引来处理。
    var a = [ ];
    a["13"] = 42;
    a.length; // 14
    
    1. 类数组
      类数组:属性要为索引(数字)属性,必须有length 属性,最好加上属性的对象
      常见类数组:arguments 对象将函数的参数当作列表来访问(从ES6 开始已废止)
      转换为真正的数组:数组工具函值数(如 slice(..), ES6 中的内置工具函数 Array.from(..) 等)来实现。
    function foo() {
      var arr = Array.prototype.slice.call( arguments );
      arr.push( "bam" );
      console.log( arr );
    }
    foo( "bar", "baz" ); // ["bar","baz","bam"]
    
    var arr = Array.from( arguments );
    
    1. 关于.运算符
      它是一个有效的数字字符,会被优先识别为数字常量的一部分,然后才是对象属性访问运算符。
    // 无效语法:
    42.toFixed( 3 ); // SyntaxError  . 被视为常量 42. 的一部分
    // 下面的语法都有效:
    (42).toFixed( 3 ); // "42.000"
    0.42.toFixed( 3 ); // "0.420"
    42..toFixed( 3 ); // "42.000"
    
    1. NaN
    判断是否为NaN
    • isNaN
    var a = 2 / "foo";
    var b = "foo";
    a; // NaN
    b; "foo"
    window.isNaN( a ); // true
    window.isNaN( b ); // true
    
    • ES6工具函数Number.isNaN()
    var a = 2 / "foo";
    var b = "foo";
    Number.isNaN( a ); // true
    Number.isNaN( b ); // false
    
    无穷除以无穷
    Infinity/Infinity
    // NaN
    
    1/Infinity
    // 0
    -1/Infinity
    // -0
    
    1. Object.is
      Object.is()是ES6新增的用来比较两个值是否严格相等的方法,与===的行为基本一致,区别不同:
    • +0不等于-0
    • NaN等于自身
    Object.is('foo', 'foo');     // true
    Object.is(window, window);   // true
    
    Object.is('foo', 'bar');     // false
    Object.is([], []);           // false
    
    var foo = { a: 1 };
    var bar = { a: 1 };
    Object.is(foo, foo);         // true
    Object.is(foo, bar);         // false
    
    Object.is(null, null);       // true
    
    // 特例
    Object.is(0, -0);            // false
    Object.is(0, +0);            // true
    Object.is(-0, -0);           // true
    Object.is(NaN, 0/0);         // true
    

    注意:能使用 == 和 === 时就尽量不要使用 Object.is(..) ,因为前者效率更高、更为通用。 Object.is(..) 主要用来处理那些特殊的相等比较。

    1. 值和引用
    var a = 2;
    var b = a; // b是a的值的一个副本
    b++;
    a; // 2
    b; // 3
    
    var c = [1,2,3];
    var d = c; // d是[1,2,3]的一个引用
    d.push( 4 );
    c; // [1,2,3,4]
    d; // [1,2,3,4]
    

    c 和 d 则分别指向同一个复合值 [1,2,3]的两个不同引用。请注意, c 和 d 仅仅是指向值[1,2,3],并非持有。所以它们更改的是同一个值(如调用 .push(4) ),随后它们都指向更改后的新值[1,2,3,4]
    由于引用指向的是值本身而非变量,所以一个引用无法更改另一个引用的指向。

    var a = [1,2,3];
    var b = a;
    a; // [1,2,3]
    b; // [1,2,3]
    
    // 然后
    b = [4,5,6];
    a; // [1,2,3]
    b; // [4,5,6]
    

    参考示例

    function foo(x) {
      x.push( 4 );
      x; // [1,2,3,4]
    
      // 然后
      x = [4,5,6];
      x.push( 7 );
      x; // [4,5,6,7]
    }
    
    var a = [1,2,3];
    foo( a );
    a; // 是[1,2,3,4],不是[4,5,6,7]
    

    我们向函数传递 a 的时候,实际是将引用 a 的一个复本赋值给 x ,而 a 仍然指向 [1,2,3] 。在函数中我们可以通过引用 x 来更改数组的值( push(4) 之后变为 [1,2,3,4] )。但 x =[4,5,6] 并不影响 a 的指向,所以 a 仍然指向[1,2,3,4]。我们不能通过引用 x 来更改引用 a 的指向,只能更改 a 和 x 共同指向的值。

    如果要将 a 的值变为 [4,5,6,7] ,必须更改 x 指向的数组,而不是为 x 赋值一个新的数组。

    function foo(x) {
      x.push( 4 );
      x; // [1,2,3,4]
    
      // 然后
      x.length = 0; // 清空数组
      x.push( 4, 5, 6, 7 );
      x; // [4,5,6,7]
    }
    
    var a = [1,2,3];
    foo( a );
    a; // 是[4,5,6,7],不是[1,2,3,4]
    

    从上例可以看出,x.length = 0x.push(4,5,6,7)并没有创建一个新的数组,而是更改了当前的数组。于是 a 指向的值变成了 [4,5,6,7]

    1. 封装对象
    var a = new Boolean( false );
    if (!a) {
      console.log( "Oops" ); // 执行不到这里
    }
    
    var a = "abc";
    var b = new String( a );
    var c = Object( a );
    typeof a; // "string"
    typeof b; // "object"
    typeof c; // "object"
    b instanceof String; // true
    c instanceof String; // true
    Object.prototype.toString.call( b ); // "[object String]"
    Object.prototype.toString.call( c ); // "[object String]"
    
    1. 拆封
      得到封装对象中的基本类型值,可以使用 valueOf() 函数:
    var a = new String( "abc" );
    var b = new Number( 42 );
    var c = new Boolean( true );
    
    a.valueOf(); // "abc"
    b.valueOf(); // 42
    c.valueOf(); // true
    
    var a = new String( "abc" );
    var b = a + ""; // b的值为"abc"
    typeof a; // "object"
    typeof b; // "string"
    
    1. 关于数组
    var a = new Array( 3 );
    var b = [ undefined, undefined, undefined ];
    var c = [];
    c.length = 3;
    a; // [empty × 3]
    b; // [undefined, undefined, undefined]
    c; // [empty × 3]
    
    a.join( "-" ); // "--"
    b.join( "-" ); // "--"
    a.map(function(v,i){ return i; }); // [empty × 3]
    b.map(function(v,i){ return i; }); // [ 0, 1, 2 ]
    

    以上均为Chrome上的测试结果
    下图为Firefox测试结果

    Firefox测试结果.png

    a.map() 之所以执行失败,是因为数组中并不存在任何单元,所以 map() 无从遍历。而join() 却不一样,它的具体实现可参考下面的代码:

    function fakeJoin(arr,connector) {
      var str = "";
      for (var i = 0; i < arr.length; i++) {
        if (i > 0) {
          str += connector;
        }
        if (arr[i] !== undefined) {
          str += arr[i];
        }
      }
      return str;
    }
    var a = new Array( 3 );
    fakeJoin( a, "-" ); // "--"
    
    1. 将原型作为默认值
    typeof Function.prototype; // "function"
    Function.prototype(); // 空函数!
    
    RegExp.prototype.toString(); // "/(?:)/" —— 空正则表达式
    "abc".match( RegExp.prototype ); // [""]
    
    Array.isArray( Array.prototype ); // true
    Array.prototype.push( 1, 2, 3 ); // 3
    Array.prototype; // [1,2,3]
    // 需要将Array.prototype设置回空,否则会导致问题!
    Array.prototype.length = 0;
    

    Function.prototype 是一个函数, RegExp.prototype 是一个正则表达式,而 Array.
    prototype 是一个数组。
    即:

    Object.prototype.toString.call(Function.prototype)
    // "[object Function]"
    
    Object.prototype.toString.call(String.prototype)
    // "[object String]"
    
    Object.prototype.toString.call(Number.prototype)
    // "[object Number]"
    
    Object.prototype.toString.call(Boolean.prototype)
    // "[object Boolean]"
    
    Object.prototype.toString.call(Array.prototype)
    // "[object Array]"
    

    但是请注意下面的情况:

    Object.prototype.toString.call(RegExp.prototype)
    // "[object Object]"
    
    Object.prototype.toString.call(/!/)
    // "[object RegExp]"
    
    Object.prototype.toString.call(new RegExp())
    // "[object RegExp]"
    

    考虑到上面的特性,这对未赋值变量是一个很好的默认值

    function isThisCool(vals = Array.prototype, fn = Function.prototype, rx = RegExp.prototype) {
      return rx.test(vals.map( fn ).join( "" ));
    }
    
    isThisCool(); // true
    
    isThisCool(["a","b","c"], function(v){ return v.toUpperCase(); }, /D/); // false
    

    这种方法的一个好处是.prototypes 已被创建并且仅创建一次。相反,如果将 []function(){}/(?:)/ 作为默认值,则每次调用 isThisCool() 时它们都会被创建一次(具体创建与否取决于 JavaScript 引擎,稍后它们可能会被垃圾回收),这样无疑会造成内存和 CPU 资源的浪费。
    另外需要注意的一点是,如果默认值随后会被更改,那就不要使用 Array.prototype 。上例中的 vals 是作为只读变量来使用,更改 vals 实际上就是更改 Array.prototype ,而这样会导致前面提到过的问题!

    相关文章

      网友评论

          本文标题:《你不知道的JavaScript》小结2

          本文链接:https://www.haomeiwen.com/subject/zguroqtx.html