对象那些事儿

Begin

此对象非彼对象....

上一篇this那些事儿介绍了this的指向,this的指向是一个对象。那么在JavaScript中对象是一个什么样的存在,一起来了解一下对象不为人知的一些故事....

开始吧!

介绍一下object家的基本情况

object是JS中基本简单类型家族6位成员之一,分别是:stringnumberbooleannullundefined,当然还有object。对象子类型开枝散叶,又被称为复杂基本类型,这里介绍一些特殊的对象子类型:

  1. function,在js中被称为头等公民,因为它们基本上就是对象,对象子类型中的内建对象也是function,地位很特殊

  2. 九种内建对象:StringNumberBooleanObjectFunctionArrayRegExpDateError。在字面形式调用属性和方法时,JS引擎自动转为内建对象,字面形式创建是推荐的,因为比较简单方便,比如:

    var strPrimitive = "I am a string";
    
    strPrimitive.length; //13
     strPrimitive.charAt( 3 )  //"m"
    复制代码

深入了解object

object的属性访问

有两种方法,.操作符和[]操作符。

  • .操作符:后面只能接标识符(字母、数字、下划线,且数字不能在首位)

  • []操作符:[]内放的是字符串的值,可以是obj["b"],也可以是var a="b";obj[a]通过一个变量返回,间接的方式

    • []操作符另一个使用是计算型属性名,在键声明位置指定一个表达式:

      var prefix = "foo";
      
      var myObject = {
      	[prefix + "bar"]: "hello",
      	[prefix + "baz"]: "world"
      };
      
      myObject["foobar"]; // hello
      myObject["foobaz"]; // world
      复制代码

复制对象

复制对象分浅拷贝和深拷贝。

  • 浅拷贝:Object.assign(){...obj}

  • 深拷贝:var newObj = JSON.parse( JSON.stringify( someObj ) );

    • 深拷贝的一个应用是数组去重,尤其是可以处理数组元素存在数组或者对象情况:

      //数组去重
      function arrElementOnly(arr) {
        arr = arr.map(ele => JSON.stringify(ele));
        let returnArr = [];
      
        arr.forEach(ele => {
          if (returnArr.indexOf(ele) === -1) returnArr.push(ele);
        })
      
        return returnArr.map(ele => JSON.parse(ele));
      }
      
      var a = [1, 1, 2];		
      arrElementOnly(a);	//[1,2]
      
      var b = [1, [1, 2],
        [1, 2]
      ];
      arrElementOnly(b);	//[1,[1,2]]
      
      var c = [1, {
        a: "1"
      }, {
        a: "1"
      }];
      arrElementOnly(c);	//[1,{a:"1"}]
      
      var d = [1, 1, 2, [1, 2],
        [1, 2], {
          a: "1"
        }, {
          a: "1"
        }
      ];
      arrElementOnly(d);	//[1,2,[1,2],{a:"1"}]
      复制代码

属性描述符

属性描述符分为数据描述符和访问器描述符。数据描述符使用Object.getOwnPropertyDescriptor(...)查看,使用Object.defineProperty(...)定义,看个例子:

var myObject = {
	a: 2
};

Object.getOwnPropertyDescriptor( myObject, "a" );	//查看数据描述符
// {
//    value: 2,
//    writable: true,
//    enumerable: true,
//    configurable: true
// }

Object.defineProperty(myObject,"b",{    //定义数据描述符
    value:3,
    writable:true,
    configurable:true,
    enumerable:true
})

myObject.b;  //3
复制代码
  • writable控制着改变属性值的能力,非严格模式下修改悄无声息失败,严格模式下抛出TypeError
  • configurable为true时,属性可配置,使用defineProperty(...)修改数据描述符
    • configurable:falsedefineProperty(...)修改数据描述符,无论strict mode与否,都会抛出TypeError,这是一个单向操作不可撤销(例外是在这个情况下,writable可由true变为false)
    • configurable:false 阻止的另外一个事情是使用 delete 操作符移除既存属性的能力 ,delete调用无声失败
  • enumerable可枚举性,控制着属性是否能在特定的对象-属性枚举操作中出现

访问器描述符之前先要谈*[[Get]][[Put]]*操作:

  • [[Get]]操作定义了属性访问的行为,如果当前对象不存在该属性,就会遍历[[Prototype]]链,如果遍历原型链不存在,返回undefined
  • [[Put]]操作定义设置属性值的行为, 属性存在访问器描述符setter吗? 调用setter:(writable=false?修改失败或者抛出TypeError:设置属性值)

访问器描述符针对某个属性覆盖[[Get]]、[[Put]]默认操作的一部分,看getter和setter:

//字面量语法定义
var myObject = {
	// 为 `a` 定义 getter
	get a() {
		return this._a_;
	},
	// 为 `a` 定义 setter
	set a(val) {
		this._a_ = val * 2;
	}
};

myObject.a = 2;
myObject.a; // 4

//使用defineProperty定义
Object.defineProperty(myObject,"b",{
    get:function(){return this.a},
    enumerable:true
})
复制代码

注意:访问器描述符不能和writablevalue一起设置,会报错Uncaught TypeError: Invalid property descriptor. Cannot both specify accessors and a value or writable attribute, #<Object>

应用
  • 将属性或对象设置为不可改变(p.s. 创建的是浅不可变性,如果对象拥有对其他对象的引用,如数组、对象、函数等 ,那个对象的内容不会受影响,依然保持可变),技术上操作数据操作符

    • 设置一个对象属性为常量(不能被改变、重定义、删除):defineProperty(...)设置属性writable:falseconfigurable:false
    • 防止一个对象添加新的属性:Object.preventExtensions(...)
    • 对象不可添加新的属性,对象所有属性不能重定义、不能删除(属性值可以更改):Object.seal(...),相当于Object.preventExtensions(...)+所有对象属性configurable:false
    • 对象不可添加新属性,对象所有属性设置为常量:Object.freeze(...),相当于Object.preventExtensions(...)+所有对象属性configurable:falsewritable:false
  • 在属性访问时得到defined有两种情况,一种属性不存在返回undefiend,另一种属性明确存储值undefined,那么该如何区分?引入属性存在性判断的问题。看个例子:

    var myObject = {
    	a: 2
    };
    
    ("a" in myObject);				// true
    ("b" in myObject);				// false
    
    myObject.hasOwnProperty( "a" );	// true
    myObject.hasOwnProperty( "b" );	// false
    复制代码

    in操作符会检查[[Prototype]]链,而hasOwnProperty(...)只会检查当前对象。你能猜到4 in [2,4,6]的结果是什么吗?

  • 属性枚举性判断。看个例子:

    var a = {
      name: "Harden"
    };
    
    var b = Object.create(a);
    
    b.age = 18;
    
    Object.defineProperty(b, "hobby", {
      value: "basketball",
      enumerable: false
    });
    
    for (pro in b) {
      console.log(b[pro]); //Harden,18
    }
    
    console.log(b.propertyIsEnumerable("name"));	//fasle  当前对象不存在
    console.log(b.propertyIsEnumerable("age"));		//true
    console.log(b.propertyIsEnumerable("hobby"));	//false  属性不可枚举 
    
    console.log(Object.keys(b));	//["age"]
    console.log(Object.getOwnPropertyNames(b));		//["age","hobby"]
    复制代码

    for..in遍历对象可枚举属性,会检查[[Prototype]]链;

    propertyIsEnumerable(...)判断在当前对象属性是否存在,Object.keys(...)返回当前对象所有可枚举属性的数组,Object.getOwnPropertyNames(...)返回当前对象所有属性。

  • for..of循环迭代值,要求被迭代的东西提供一个迭代器对象。先看一下数组:

    数组使用for..of迭代是ok的,因为数组拥有内建的@@iterator

    var a = [1, 2, 3, 4];
    for (value of a) {
      console.log(value);	1,2,3,4
    }
    
    //手动使用@@iterator
    var b = [1,2,3];
    var it = b[Symbol.iterator]();
    console.log(it.next());		//{ value:1, done:false }
    console.log(it.next());		//{ value:2, done:false }
    console.log(it.next());		//{ value:3, done:false }
    console.log(it.next());		//{ done:true }
    复制代码

    使用Symbol.iterator取得数组的一个内建对象@@iterator@@iterator是一个函数,函数运行返回一个迭代器对象,这样就可以it.next()迭代。再看对象:

    var myObject = {
      a: 2,
      b: 3
    };
    
    for (value of myObject) {
      console.log(value);
    }	//oops! Uncaught TypeError: myObj is not iterable
    复制代码

    对象没有内建的@@iterator,可以自定义迭代器:

    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() {
            let value = o[ks[idx++]],
              done = idx > ks.length;
            return { value, done };
          }
        };
      }
    });
    
    //手动迭代
    var it = myObject[Symbol.iterator]();
    console.log(it.next());
    console.log(it.next());
    console.log(it.next());
    
    for (value of myObject) {
      console.log(value);
    }
    复制代码

End

文章为个人总结,不妥之处还请雅正。

转载请注明出处。

参考文献:

你不懂JS:this与对象原型 第三章:对象

猜你喜欢

转载自juejin.im/post/5b6a8b13f265da0fa21aab66
今日推荐