《你不知道的JavaScript》小结2

2019-05-15  本文已影响0人  Mr君
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
var a = 2 / "foo";
var b = "foo";
a; // NaN
b; "foo"
window.isNaN( a ); // true
window.isNaN( b ); // true
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新增的用来比较两个值是否严格相等的方法,与===的行为基本一致,区别不同:
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 ,而这样会导致前面提到过的问题!

上一篇下一篇

猜你喜欢

热点阅读