你可能不想知道的js

Posted by Turingdo Studio on November 23, 2020

你不知道1

  1. typeof null === ‘object’ 原理

    原理是这样的,不同的对象在底层都表示为二进制,在 JavaScript 中二进制前三位都为 0 的话会被判 断为 object 类型,null 的二进制表示是全0, 自然前三位也是0,所以执行typeof 时会返回 “object”

  2. 原始值 “I am a string” 并不是一个对象,它只是一个字面量,并且是一个不可变的值。

  3. 我们都可以直接在字符串字面量上访问属性或者方法,之所以可以这 样做,是因为引擎自动把字面量转换成 String 对象,所以可以访问属性和方法

  4. 在对象中,属性名永远都是字符串。如果你使用 string(字面量)以外的其他值作为属性 名,那它首先会被转换为一个字符串。即使是数字也不例外,虽然在数组下标中使用的的 确是数字,但是在对象属性名中数字会被转换成字符串,所以当心不要搞混对象和数组中 数字的用法

  5. 对于 JSON 安全(也就是说可以被序列化为一个 JSON 字符串并且可以根据这个字符串解 析出一个结构和值完全一样的对象)的对象来说,有一种巧妙的复制方法 var newObj = JSON.parse( JSON.stringify( someObj ) );当然,这种方法需要保证对象是 JSON 安全的,所以只适用于部分情况

  6. 属性描述符 Object.getOwnPropertyDescriptor(obj, attrName) 返回四个值 ,分别是 value writable enumerable configuerable

    • writable 决定是否可以修改属性的值

    • configuerable 只要属性是可配置的,就可以使使用 defineProperty(…) 方法来修改属性描述符

      Object.defineProperty(myObject, 'a', {
         value: 4,
         writable: true,
         enumerable: true,
         configuerable: true,
      })
      
    • enumerable 控制属性是否会出现在 for in 循环中

1) 对象常量 结合 writable:false 和 configurable:false 就可以创建一个真正的常量属性(不可修改、 重定义或者删除

   var myObject = {}; 
   
   Object.defineProperty( myObject, "FAVORITE_NUMBER", {     
   value: 42,     
   writable: false,     
   configurable: false  
   } );

2) 禁止扩展

如果你想禁止一个对象添加新属性并且保留已有属性,可以使用 Object.preventExtensions(..)

3) 密封

Object.seal(..) 会创建一个“密封”的对象,这个方法实际上会在一个现有对象上调用 Object.preventExtensions(..) 并把所有现有属性标记为 configurable:false。

所以,密封之后不仅不能添加新属性,也不能重新配置或者删除任何现有属性(虽然可以 修改属性的值)

4) 冻结

Object.freeze(..) 会创建一个冻结对象,这个方法实际上会在一个现有对象上调用 Object.seal(..) 并把所有“数据访问”属性标记为 writable:false,这样就无法修改它们 的值。

  1. 在对象中,属性名永远都是字符
  > 如果你使用 string(字面量)以外的其他值作为属性 名,那它首先会被转换为一个字符串。即使是数字也不例外,虽然在数组下标中使用的的 确是数字,但是在对象属性名中数字会被转换成字符串,.
  1. 可计算属性名最常用的场景可能是 ES6 的符号(Symbol) , 不过 简单来说,它们是一种新的基础数据类型,包含一个不透明且无法预测的值(从技术角度来说就是字符串)

  2. 深拷贝 var newObj = JSON.parse( JSON.stringify( someObj ) ); ,这种方法需要保证对象是 JSON 安全的,所以只适用于部分情况。

  3. 用户定义的所有的普通属性默认都是 enumerable,这通常就是你想要的。但是如果你不希 望某些特殊属性出现在枚举中,那就把它设置成 enumerable:false。对于一些自定义特别标识的属性 可以这样操作。

  4. in 操作符会检查属性是否在对象及其 [[Prototype]] 原型链中。相比之下, hasOwnProperty(..) 只会检查属性是否在 myObject 对象中,不会检查 [[Prototype]] 链。

  5. 和数组不同,普通的对象没有内置的 @@iterator,所以无法自动完成 for..of 遍历。之所 以要这样做,有许多非常复杂的原因,不过简单来说,这样做是为了避免影响未来的对象 类型。

  6. 你可以给任何想遍历的对象定义 @@iterator

    var myObject = {
        a:2,
        b:3
    };
    Object.defineProperty(myObject, Symbol.iterator, {
        enumerable: false,
        writable: false,
        configurable: true,
        value: function(){
            var o = this;
            var idx = 0;
            var ks = Object.keys(o);
            return {
                next: function(){
                    return {
                        value: o[ks[idx++]],
                        done:(idx > ks.length)
                    }
                }
            }
        },
    })
           
    // 也可以直接在定义对象时进行声明,比如 
    var myObject = {
        a: 2,
        b: 3,
        [Symbol.iterator]: function() {
            return {
                next: function(){
                    return {
                        value, done
                    }
                }
            }
        }
    }
    
  7. 你甚至可以定义一个“无限”迭代器,它永远不会“结束”并且总会返回一个新 值(比如随机数、递增值、唯一标识符,等等)。你可能永远不会在 for..of 循环中使用这 样的迭代器,因为它永远不会结束,你的程序会被挂起:

    var randoms = {
        [Symbol.iteraror]:function(){
            return {
                next: function(){
                    return { value: Math.random }
                }
            }
        }
    }
    var randoms_pool = [];
    for(var n of randoms) {
        randoms_pool.push(n);
        // 防止无限运行
        if(randoms_pool.length === 100) break;
    }
    
  8. Object.create()的polyfill代码

    if(!Object.create){
        Object.create = function(o){
            function F (){}
            F.prototype = o;
            return new F();
        }
    }
           
    
  9. “修复”丢失的 .constructor 属性

    function Foo() { /* .. */ }

    Foo.prototype = { /* .. */ }; // 创建一个新原型对象

    // 需要在 Foo.prototype 上“修复”丢失的 .constructor 属性

    // 新对象属性起到 Foo.prototype 的作用

    Object.defineProperty(Foo.prototype, 'construtor', {
        enumerable: false,
        writable: true,
        configuable: true,
        value: Foo, // 让.construtor 指向Foo
    })
    

    .constructor 并不是一个不可变属性。它是不可枚举(参见上面的代码)的,但是它的值 是可写的(可以被修改)。此外,你可以给任意 [[Prototype]] 链中的任意对象添加一个名 为 constructor 的属性或者对其进行修改,你可以任意对其赋值。

    1. 创建一个合适的关联对象,我们必须使用 Object.create(..) 而不是使用具有副 作用的 Foo(..)。这样做唯一的缺点就是需要创建一个新对象然后把旧对象抛弃掉,不能 直接修改已有的默认对象。
    我们来对比一下两种把 Bar.prototype 关联到 Foo.prototype 的方法:
    
    // ES6 之前需要抛弃默认的Bar.prototype
    Bar.prototype = Object.create(Foo.prototype);
        
    // ES6 开始可以直接修改现有的Bar.prototype
    Object.setPrototypeof(Bar.prototype, Foo.prototype)
    
  10. 获取一个对象的原型链 Object.getPrototypeof(a), 也就是 Object.getPrototypeof(a) === Foo.prototype

也有一种非标准的方法来访问内部原型链的属性 `a._proto_ === Foo.prototype`
  1. __proto__的实现原理
```javascript
Object.definePrototy(Object.prototype, '__proto__',{
    get: function(){
        return Object.getPrototypeof(this)
    },
    set: function(o){
        // es6 的setProtoOf(..)
        Object.setPrototypeOf(this, o);
        return o;
    }
})
```
  1. 理解 Object.create(..) 这个大英雄

    var foo = {
        something: function(){
            console.log('Tell me somgthing good...')
        }
    };
    var bar = Object.create(foo);
    bar.somgthing(); // Tell me somgthing good...
        
    // Object.create(..) 会创建一个新对象(bar)并把它关联到我们指定的对象(foo),这样 我们就可以充分发挥 [[Prototype]] 机制的威力(委托)并且避免不必要的麻烦(比如使 用 new 的构造函数调用会生成 .prototype 和 .constructor 引用)。
        
    
  2. Object.create(null) 会创建一个拥有空(null)的原型链对象

    Object.create(null)            null[[Prototype]] 链接的对象这个对象无法进行委托由于这个对象没有原型链所以 instanceof 操作符之前解释过无法进行判断因此总是会返回 false 这些特殊的空 [[Prototype]] 对象通常被称作字典”,它们完全不会受到原 型链的干扰因此非常适合用来存储数据
        
    
  3. :[[Prototype]] 机制就是指对象中的一个内部链接引用 另一个对象。

    如果在第一个对象上没有找到需要的属性或者方法引用,引擎就会继续在 [[Prototype]] 关联的对象上进行查找。同理,如果在后者中也没有找到需要的引用就会继续查找它的 [[Prototype]],以此类推。这一系列对象的链接被称为“原型链”。

    JavaScript 中这个机制的本质就是对象之间的关联关系。