理解JavaScript对象

266 阅读5分钟

对象的定义:“无序属性的集合,其属性可以包含基本值、对象或函数”。

首先,我们创建一个最简单的对象,

const person = new Object()

或者是这样,使用对象字面量的方式

const p = {};

对象是引用类型,通过指针访问,array、function实际也是对象。

引用类型的值是保存在堆内存中的对象。 当我们访问p,实际上我们通过指针找到内存中的对象。

const p1 = p;
p1.age = 20;
console.log(p.age)

以上这段代码的结果是20。当我们直接复制引用类型的值时,得到的是一个指针,这个指针指向堆内存中的一个对象,也就是p和p1是指向内存中的同一个对象。

理解对象原型链

function Person(){};
Person.prototype.say = "hello";
const p = new Person;
console.log("say" in p);//true
console.log(p.hasOwnProperty("name"));//false

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

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

const people = {
	run : true
};
const obj = Object.create(people);

这样obj的原型链上就有了people,因此我们可以访问obj.run。 当我们添加实例对象的属性时,并不会操作原型中的属性。例如:

obj.run = 'fast';
delete obj.run;
obj.hasOwnProperty('run');//false
Object.getPrototypeOf(obj);//{run:true}

我们在实例上创建了同名属性'run',这个属性会屏蔽原型对象中的保存的同名属性。 此时obj.run为fast,接着我们删除了实例对象的属性,那么它应该为undefined,但是在原型上存在run属性,所以访问结果为true。

每当读取某个对象的属性时,会搜索属性名称,首先搜索对象实例,如果存在该属性返回属性的值,如果不存在,则继续去原型链中查找给定名称的属性。

继承

function Animal(age){
	this.eat = true;
    this.age = age;
};
Animal.prototype.run = "fast";
function People(name,age){
	this.name = name;
    Animal.call(this,age) // 注释1
    this.say = function(){
    	console.log('I can speak')
    }
};
console.log(People.prototype.constructor) // 注释3+ '[Function:People]'

People.prototype = new Animal(12); // 注释2
People.prototype.constructor = People; // 注释3
People.prototype.hi = function(){ // 注释4
	console.log('hi,'+ 'I am ' + this.name )
};
const jack = new People('jack',18);
Object.getPrototypeOf(jack);

Object.getPrototypeOf() 方法返回指定对象的原型(内部Prototype属性的值)。以下为结果

People {
  eat: true,
  age: undefined,
  constructor: [Function: People],
  hi: [Function]
}

注释1 :People调用Aniaml的构造函数,从而继承它的属性,并且实例拥有自己的属性age。

注释2 :这里把Animal实例化(Animal构造函数与原型链属性),并且放置到People的prototype上,People的实例可以借助原型链访问run属性。此时它的constructor已经指向Animal

(只要我们创建了一个新的函数,会自动为该函数创建一个prototype属性,它指向函数的原型对象。默认情况下该函数的prototype.constructor是一个指向该函数的指针。注释3+)

注释3 :修改People的constructor指向自身。实例化的对象本事跟构造函数没有任何关系,只不过People实例('jack')的prototype.constructor指向了People。(通过Object.getPrototypeOf('jack')可以查看)

注释4 : 这个操作看起来没有任何意义,它只是提醒你,在People.prototype添加属性或者方法跟Animal没有任何关系,新生成的Animal实例包括原型链没有办法找到该属性,但是新生成的People实例可以通过原型链查到该属性。

请问一下jack的age是多少?你是怎么理解的?

使用ES6重写案例

class Animal{
	constructor(age){
		this.eat = true;
		this.age = age
	}
	run = 'fast'
}

class People extends Animal{
	constructor(name,age){
		super(age)
		this.name = name
	}
	hi(){
		console.log(`hi,I am ${this.name}`)
	}
}

不管是类还是继承,ES6的写法都更简洁且便于理解,python程序员觉得很nice!!!

现在我们看一下对象本身的属性特征。

数据属性:

Configurable :表示能否通过delete删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性。这个特性的默认值为true。

Enumerable : 表示能否通过for-in循环返回属性。这个特性的默认值为true。

Writable : 表示能否修改属性的值。这个特性的默认值为true。

Value : 包含这个属性的数据中值。读取属性的时候,从这个位置读;写入属性值的时候,把新值保存在这个位置。这个特性的默认值为undefined。

访问器属性 :

Configurable ...

Enumerable ...

Get : 读取属性时调用的函数。默认值为undefined。

Set : 写入属性时调用的函数。默认值为undefined。

const p = {
	name : '小明',
    age : 18,
    education : 12
}
Object.getOwnPropertyDescriptor(p,'name')
//{ value: '小明', writable: true, enumerable: true, configurable: true }

Object.getOwnPropertyDescriptor() 方法返回指定对象上一个自有属性对应的属性描述符。(自有属性指的是直接赋予该对象的属性,不需要从原型链上进行查找的属性)

默认情况下数据属性都是true,现在我们尝试修改一下。

Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象

Object.defineProPerty(p,'name',enumerable:false);
p.propertyIsEnumerable('name') //false

重新定义属性之后,'name'已经不可枚举。在调用Object.defineProperty()方法创建一个新的属性时,如果不指定,configurable、enumerable和writable的特性都是false。如果只是修改已定义的属性的特性无此限制。

注意 :一旦把某个属性的特性configurable修改为false,那么在去调用Object.defineProPerty()方法就会报错。

const 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
		}
	}
})

book.year = 2020
console.log(book)

在控制台打印book对象,可以看到_year和edition属性值已经改变,但是没有看到year属性,因为它是不可枚举的。