js-对象Object

81 阅读7分钟

说明

对象API的比较在于:

  1. 是Obejct静态方法、或通过实例调用原型方法

  2. 是否能够取到原型的属性

  3. 是否能够检索非枚举属性

  4. 是否改变原有对象

  5. 参数、返回值

ECMAScript 中有两种对象属性:数据属性和访问器属性。

对象中属性

  • 数据属性

  • [[Configurable]]

  • 不能删除该属性,也不是重新define该属性

  • [[Enumerable]]

  • [[Writable]]

  • [[Value]]

  • 访问器属性

  • [[Configurable]]

  • [[Enumerable]]

  • [[Set]]

  • [[Get]]

    var person = {}; Object.defineProperty(person, "name", { configurable: false, value: "Nicholas" }); alert(person.name); //"Nicholas" delete person.name; alert(person.name); //"Nicholas"

    // get、set与value是互斥的 var book = { _year: 2004, edition: 1 }; Object.defineProperty(book, "year", { get: function(){ return this._year; }, set: function(newValue){ if (newValue > 2004) { this._year = newValue; this.edition += newValue - 2004; } } });

对象的可枚举性

添加一个对象的属性默认是可枚举的。enumerable

概念

可枚举性(enumerable)用来控制所描述的属性,是否将被包括在for...in循环之中。具体来说,如果一个属性的enumerable为false,下面三个操作不会取到该属性。

  1. for..in循环

  2. Object.keys()方法

  3. JSON.stringify()方法

obj.getOwnPropertyNames();可以取到非枚举值。

enumerable “隐身术”

var o = {a:1, b:2};
o.c = 3;

Object.defineProperty(o, 'd', {
  value: 4,
  enumerable: false
});


o.d
// 4

for( var key in o ) console.log( o[key] ); 
// 1
// 2
// 3

console.log(Object.keys(o));  // ["a", "b", "c"]

console.log(JSON.stringify(o)); // "{a:1,b:2,c:3}"

console.log(Object.getOwnPropertyNames(o)); // ['a', 'b', 'c', 'd']

上面代码中,d属性的enumerablefalse,所以一般的遍历操作都无法获取该属性,使得它有点像“秘密”属性,但还是可以直接获取它的值。

至于for...in循环和Object.keys方法的区别,在于前者包括对象继承自原型对象的属性,而后者只包括对象本身的属性。如果需要获取对象自身的所有属性,不管enumerable的值,可以使用Object.getOwnPropertyNames方法。

当然啦,for in获取原型属性也不会包括js内置的对象属性,只是用户自定义的。

默认不可访问原型上的属性和方法包括:hasOwnProperty()、 propertyIsEnumberable()、toString() 、valueOf()

如何设置enumerable

// 方式1. 创建对象后 添加属性
var o = {a:1, b:2};
Object.defineProperty(o, 'd', {
  value: 4,
  enumerable: false
});

// 方式2. 在创建对象时就定义
var my_obj = Object.create({}, {
  getFoo: {
    value: function() { return this.foo; },
    enumerable: false
  }
});

访问、添加、修改

var obj = {
    sex : "不详",
    socre : 100,
    flag : true,
    sing : function{
       console.log("爱唱歌")
      },
    play : function{
       console.log("打游戏")
      }
  }
 
对象的操作:

访问值:(查)
console.log(obj.sex);
console.log(obj["socre"]);
console.log(obj.sing());
console.log(obj['sing']());

 
添加一个属性:(增)
obj.hobby = "睡觉";
obj["weight"] = “50kg”;
obj.dance = function(){}

修改:(改)
obj.sex = “女”;
obj["socre"] = 99;

 
删除:(删)
delete obj.flag;
delete obj["sex"];
注意:delete删除对象的某个属性时就删除了,
但是在删除数组中某个元素时会留下空位

访问属性的时候,可以用obj.属性名或者obj["属性名"];需要执行方法的时候,需要用“.”的方式去调用。

请避免将字符串、数值或布尔声明为对象。他们会增加代码的复杂性并降低执行速度。

定义一个私有属性

1.闭包

2.构造函数:定义局部变量,get和set方法暴露出来

3.class中

class Person {
  constructor(name, age) {
    // 私有属性
    let _name = name;
     
    this.age = age;
    this.setName = function (name) {
      _name = name;
    };
    this.getName = function () {
      return _name;
    };
  }
}

循环遍历对象

for in

遍历出来的是属性名

// eg:1
var obj = {
  name : 'wanghang',
  age : 18,
  sex : 'males',
  load:function(){
    console.log('do');
  }
}
   
for(let prop in obj){
  //prop对应 obj中的”name”,”age”,”sex”
  //因为底层原理obj.prop --->obj[‘prop’]所以遍历之后没有结果
  // console.log(obj.prop);// 错误遍历方式
  console.log(typeof prop); // string
  console.log(obj[prop]);//正确遍历方式
}


// eg:2
var obj = {a:1, b:2, c:3};

for (var prop in obj) {
  console.log("obj." + prop + " = " + obj[prop]);
}

// Output:
// "obj.a = 1"
// "obj.b = 2"
// "obj.c = 3"

官网:for...in不应该用于迭代一个关注索引顺序的 [Array](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Array)

一般说for...in 就不用于数组

估计是遍历时顺序不能保证?不是这么说的

Array.prototype.eee = 'eee'
var arr = ['aaa','bbb','ccc','ddd']
console.log(arr)
for (var i in arr){
	console.log(arr[i])
}
// 因为也会遍历出Array原型对象上人为定义的属性,
// 有一些库会去增加Array上的原型属性的,这样把这些属性也遍历出来就好了嘛

for-in循环会遍历实例和原型链上可枚举的所有属性,屏蔽了所有不可枚举属性。

Object.keys()

作用:用于返回对象可枚举的、包括实例不包括原型的属性名称数组。

参数:【Object】 必传

var a = {name : 'kong', age : 18, func : function(){}};
Object.keys(a); //['name', 'age', 'func']

与Obejct.getOwnPropertyNames()的区别:

都是只能获得自己实例的属性,而不能获得原型上的属性。

Object.keys():不能获取对象的不可枚举属性。

Object.getOwnPropertyNames():能获得对象的不可枚举属性。

Object.getOwnPropertyNames()

官方:

**Object.getOwnPropertyNames()**方法返回一个由指定对象的所有自身属性的属性名(包括不可枚举属性但不包括Symbol值作为名称的属性)组成的数组。

df:得到对象的key的数组。也不包括原型上的key

参数:【Object】 一个对象

对象遍历方法比较

是否能作用原型上的属性

是否能作用非枚举的属性

for in

Object.keys()

Object.getOwnPorpertyNames()

object.hasOwnProperty()

in

这也说明了:没有啥办法能够遍历出原型上的对象内置方法,没有两个都‘是’的方法。

总结js中带in的forin和in,都可以访问原型属性。带property的api都可以访问非枚举属性。

对象属性方法

Object.is()

作用:用于判断两个对象是否相同

参数:第一个 【Any】

第二个 【Any】

返回:【Boolean】

Object.is(a, b);//返回true或false

//注意,该函数与==运算符不同,不会强制转换任何类型,
应该更加类似于===,但值得注意的是它会将+0和-0视作不同值

1.+0与-0
console.log( 0 === -0); // true
console.log(Object.is(0,-0)); // false

2.NaN
console.log( NaN === NaN); // false
console.log(Object.is(NaN,NaN)); // true
console.log(Object.is(0/0,NaN)); // true

Object.create(proto,[propertiesObject])

Object.create()方法创建一个新对象,使用参数对象来提供新创建的对象的__proto__。

其实这不是克隆作用,到像是继承。

返回:新的【Object】

var Plane = function(){
this.blood = 100;
this.attackLevel = 1;
this.defenseLevel = 1;
};
var plane = new Plane();

var clonePlane = Object.create( plane );
console.log( clonePlane ); // 输出:Object {blood: 500, attackLevel: 10, defenseLevel: 7}

//在不支持 Object.create 方法的浏览器中,则可以使用以下代码:
Object.create = Object.create || function( obj ){
  var F = function(){};
  F.prototype = obj;
  return new F();
}

Object.assign()

es6新增

用于合并对象,给目标对象添加另一个对象的全部属性

参数:

第一个参数:【Object】 结果对象

第二个参数: 【Object】 目标对象

。。。:【Object】 目标对象

返回:第一个参数的引用,就是修改后的第一个参数对象的引用

var first = {name : 'kong'};
var last = {age : 18};
var person = Object.assign(first, last);
console.log(person);//{name : 'kong', age : 18}
console.log(first);//{ name: 'kong', age: 18 }
console.log(last);//{ age: 18 }
console.log(person === first); // true

案例:

Object.assign 方法可以很方便地一次向类添加多个方法。

class Point {
 constructor(){
 // ...
 } }
Object.assign(Point.prototype, {
 toString(){},
 toValue(){} 
 }
);

Object.defineProperty()

作用:劫持变量的set和get方法,将属性添加到对象,或修改目标属性的特性。修改属性的特性的。

返回:并返回此对象,或则一个{}空对象。

参数:

1.【Object】目标对象

2.【String】key的字符串,目标对象的属性名

3.【Object】get、set方法、value、enumerable

注意:千万不能递归获取和设置。

这个api是唯一能让delete删除对象属性返回为false的;

案例1var a = {
  name:'dingfeng'
};
console.log(a.name); // dingfeng

Object.defineProperty(a, 'name', {
	value : 'kong',
	enumerable : true	//该属性是否可枚举
})
console.log(a.name); // kong

案例2const data = {};
let name = "张三";

Object.defineProperty(data,'name',{
    get:function(){
        console.log('触发get')
        return name
        // return data.name // 递归导致内存泄漏
    },
    set:function(newVal){
        console.log('触发set')
        name=newVal
        // data.name = newVal //递归导致内存泄漏
    }
})

//测试
console.log(data.name)   // 触发get  张三
data.name = '李四'         // 触发set


案例3
const data = {
    person:[]
};

let obj = Object.defineProperty(data,'person',{
    get:()=>{
        console.log('get');
        return {}
    },
    set:()=>{
        console.log('set');
    },
})


data.person.push(1);  // 报错:没有这个方法。可见vue中并不是。

解决递归设置获取操作时内存泄露问题:使用函数this对象 or 使用函数闭包特性

	  /**
    * 方式一:使用函数this对象
		**/

			let data = {
				name:'尚硅谷',
				address:'北京',
			}

			//创建一个监视的实例对象,用于监视data中属性的变化
			const obs = new Observer(data)		
			console.log(obs)	

			//模拟一个vm实例对象
			let vm = {}
			vm._data = data = obs

			function Observer(obj){
				//汇总对象中所有的属性形成一个数组
				const keys = Object.keys(obj)
				//遍历
				keys.forEach((k)=>{
					// dingfeng: 这个this玩的很6啊
					Object.defineProperty(this,k,{
						get(){
							return obj[k]
						},
						set(val){
							console.log(`${k}被改了,我要去解析模板,生成虚拟DOM.....我要开始忙了`)
							obj[k] = val
						}
					})
				})
			}


/**
方式二:利用函数闭包
*/


let obj = {
  name: 123,
};

function defineReactive(data, key, val) {
  Object.defineProperty(data, key, {
    get() {
      console.log('你试图访问obj的' + key + '属性');
      return val;
    },
    set(newValue) {
      console.log('你试图修改obj的' + key + '属性', newValue);
      if (val === newValue) {
        return;
      }
      val = newValue;
    },
  });
}

defineReactive(obj, 'name', obj.name);
console.log(obj);
console.log(obj.name);
obj.name = '张三';
console.log(obj);

注意:该api还有一个特性,比如获取obj.a.m.n 时每次"点"都会触发getter函数

Object.defineProperties()

…可添加多个属性,与Object.defineProperty()对应,

Object.defineProperties(a, {
	name : {
		value : 'kong',
		enumerable : true
	},
	job : {
		value : 'student',
		enumerable : true
	}
})

Obj.prototype.isPrototypeof()

确定一个对象是否存在于另一个对象的原型链中

function A(){}
var a = new A();
console.log(A.prototype.isPrototypeOf(a));//true
console.log( a instanceof A);  // true

Object.getPrototypeOf()

用来得到原型对象

同来替代__proto__

解决__proto__是不规范的,是各大浏览器厂商各自添加的

可以用来从子类上获取父类

因此,可以使用这个方法判断,一个类是否继承了另一个类。

Object.getPrototypeOf(ColorPoint) === Point
// true

Object.getOwnPropertyDescriptor()

获取指定属性的属性描述符

class CustomHTMLElement {
 constructor(element) {
 this.element = element;
 }
 get html() {
 return this.element.innerHTML;
 }
 set html(value) {
 this.element.innerHTML = value;
 } }
var descriptor = Object.getOwnPropertyDescriptor(
 CustomHTMLElement.prototype, "html"
);
"get" in descriptor // true
"set" in descriptor // true

Object.getOwnPropertyDescriptors()

ES2017新增

Object.getOwnPropertySymbols()

判断对象属性是否存在

in

检查某个键名是否存在的运算符in,适用于对象,也适用于数组。

如果数组的某个位置是空位,in运算符返回false。

但是数组使用的方式不太一样,它是指数组的键名就是索引, 参数String会被转成number,所以在数组中也没啥用

用在对象时,注意是string类型哦,写数字的时候会自动转成string的,只是感觉不出来

const a = {
    hello:'1213'
}
console.log('hello' in a) // true

const b = ['12','123','34']
console.log(1 in b) // true
console.log('1' in b) // true
console.log('123' in b) // false

  每当代码读取某个对象的某个属性时,都会执行一次搜索,目标是具有给定名字的属性。搜索首先从对象实例本身开始。如果在实例中找到了具有给定名字的属性,则返回该属性的值;如果没有找到,则继续搜索指针指向的原型对象,在原型对象中查找具有给定名字的属性。如果在原型对象中找到了这个属性,则返回该属性的值。虽然可以通过对象实例访问保存在原型中的值,但却不能通过对象实例重写原型中的值。如果在实例中添加一个与原型中属性同名的属性,则该属性会屏蔽原型中的那个属性。添加的同名属性只会阻止我们访问原型中的那个属性,但不会修改那个属性。即使将这个属性设置为null,也只会在实例中设置这个属性,而不会恢复其指向原型的链接。

in操作符只要通过对象能访问到属性就返回true。会去找原型中的属性。

hasOwnProperty()只在属性存在于实例中时才返回true。不会去找原型的属性。

使用delete操作符则可以完全删除对象实例属性,从而让我们能巩固重新访问原型中的属性,不会删除原型属性。

// 好好看,这段代码很好
function Person(){
}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
    alert(this.name);
};

var person1 = new Person();
var person2 = new Person();

alert(person1.hasOwnProperty("name"));//false
alert("name" in person1);//true

person1.name = "Greg";
alert(person1.name);//"Greg"
alert(person1.hasOwnProperty("name"));//true
alert("name" in person1);//true

delete person1.name; // delete 不会删除原型属性
alert(person1.name);//"Nicholas"
alert(person1.hasOwnProperty("name"));//false
alert("name" in person1);//true

obj.hasOwnProperty()

方法会返回一个布尔值,指示对象自身属性中是否具有指定的属性(也就是,是否有指定的键)。

对象实例调用

参数:【String】【必须】需要查找的键名

返回:【Boolean】

不可获取原型属性,可获取非枚举属性

eslint校验后,需要使用

Object.prototype.hasOwnProperty.call(obj,"name")

因为obj.hasOwnProperty()可能会被一些库重写

// eg1:不可获取原型属性
var triangle = {a: 1, b: 2, c: 3};

function ColoredTriangle() {
  this.color = 'red';
}

ColoredTriangle.prototype = triangle;

var obj = new ColoredTriangle();

for (var prop in obj) {
  if (obj.hasOwnProperty(prop)) {
    console.log(`obj.${prop} = ${obj[prop]}`);
  } 
}
// Output:
// "obj.color = red"

//eg2:不可获取原型属性
var a = {name : 'kong', age : 18, func : function(){}};
a.__proto__.gener = '男'
console.log(a.hasOwnProperty('name')); // true
console.log(a.hasOwnProperty('gener')); // false



// eg2:可获取非枚举属性
var a = {name : 'kong', age : 18, func : function(){}};
Object.defineProperty(a, 'gener', {
  value: '男',
  enumerable: false
});
console.log(a.hasOwnProperty('name')); // true
console.log(a.hasOwnProperty('gener')); // true

===

给对象赋值

返回赋的值

给对象赋值,会返回赋的值,oh太神奇了。

var Days = {};
console.log(Days["Sun"] = 0);
console.log(Days["Sun1"] = 100);
console.log(Days["Sun2"] = 'qwe');


0
100
qwe

对象key的顺序

对象key的顺序能够保持定义时的顺序吗?

答:不一定

规则:正整数会移动到最前,并且按从小到大排列,不管定义时的顺序;其他类型的key会安装定义时的顺序排列。

const obj2 = {
  a:null,
  c:null,
  "-1":null,
  b:null,
  1:null,
  0:null
}
console.log(obj2);
console.log(Object.keys(obj2));
console.log(Object.getOwnPropertyNames(obj2));
for (const key in obj2) {
  console.log(key);
}

// 0 1 a c -1 b