从新认识javascript对象(一)——对象及其属性

1、建立对象

javascript中有三种方法能够建立一个对象:javascript

  1. 对象字面量
var obj = {
    name: 'jack',
    age: 12
}
复制代码
  1. new 构造函数
var obj = new Object()
var obj1 = new Object({
    name: 'jack',
    age: 12
})
复制代码
  1. Object.create()
var obj = Object.create({
    name: 'jack',
    age: 12
})
复制代码

须要注意的是经过Object.create()建立的对象实际上等于将该对象的__proto__指向Object.create()里面的参数对象,而obj自己是个空对象。java

var obj = Object.create({
    name: 'jack',
    age: 12
})
// 等价于 obj.__proto__ = { name: 'jack', age: 12 }
console.log(obj)  // {}
console.log(obj.__proto__)  // { name: 'jack', age: 12 }
obj.toString()   // '[object Object]'
复制代码

若是往Object.create()里面传入的是null,则建立的对象不继承Object的任何方法及属性。bash

var obj = Object.create(null)
console.log(obj)  // {}
console.log(obj.__proto__)  // undefined
obj.toString()  // 报错
复制代码

若是想建立一个空对象,须要传入Object.prototype函数

var obj = Object.create(Object.prototype)
// 和 {} 、new Object()同样
复制代码

2、对象的属性

咱们知道,对象的属性是由名字、值和一组特性组成(属性的特性待会介绍)。在ES5中属性值能够用一个或两个方法代替,这两个方法就是gettersetter。由gettersetter定义的属性称为“存储器属性”,它不一样于数据类型的属性,数据属性只有一个简单的值。咱们重点讲解存储器属性。ui

当咱们查询存储器属性时会调用getter方法(无参数)。这个方法返回值就是属性存取表达式返回的值。this

当咱们设置存储器属性时会调用setter方法(有参数)。这个方法修改存储器属性的值。spa

var obj = {
    num: 12,
    age: 13,
    get num1 () {
        return this.num
    },
    set num1 (value) {
        this.num = value
    },
    get age1 () {
        return this.age
    }
}
obj.num1   // 12
obj.num1 = 120
obj.num1  // 120

obj.age1  // 13
obj.age1 = 130
obj.age1  // 13
复制代码

存储器属性定义为一个或者两个和属性同名的函数,这个函数定义没有使用function关键字而是使用getsetprototype

能够看出若是该属性只有getter方法则只能读取该属性不能设置该属性,一样若是只有setter方法就只能设置该属性,不能读取该属性,只有当二者都有时才能正常读取和设置属性。code

3、对象属性的特性

每一个对象的数据属性都有四个特性(也能够说是属性描述符),分别为:对象

  1. value 属性的值
  2. writable 可写性,若是为false该值将不能被修改
  3. enumerable 可枚举性,若是为false将不能被枚举
  4. configurable 可配置性,若是为false将不能被配置,即不能被delete操做符删除,不能更改这四个特性,一旦设为false则没法再设为true,也就是一个不可逆过程

前面咱们讲过存储器属性,每隔对象的存储器属性一样也有四个特性,分别为:

  1. get
  2. set
  3. enumerable
  4. configurable

若是想要得到一个对象某个属性的这四个特性,能够调用 Object.getOwnPropertyDescriptor() 方法,该方法接受两个参数,第一个为对象,第二个为对象的属性

var obj = {
    name: 'jack',
    age: 12,
    get age1 () {
        return this.age1
    },
    set age1(value) {
        this.age1 = value
    }
}
// 获取数值属性的特性
Object.getOwnPropertyDescriptor(obj,'name')
// {value: "jack", writable: true, enumerable: true, configurable: true}

// 获取存储器属性的特性
Object.getOwnPropertyDescriptor(obj,'age1')
// {enumerable: true, configurable: true, get: ƒ, set: ƒ}

// 试图获取不存在的属性,返回undefined
Object.getOwnPropertyDescriptor(obj, 'sex')  // undefined

// 试图获取原型上的属性,返回undefined 
Object.getOwnPropertyDescriptor(obj, 'toString')  // undefined
复制代码

从上面能够看出,Object.getOwnPropertyDescriptor() 只能获得自有属性的描述符,要想得到继承属性的特性,咱们能够把该对象的原型传进去。

function Person () {
    this.name = 'sillywa'
}
Person.prototype.sex = 'boy'
Person.prototype.age = 13
var person1 = new Person()
Object.getOwnPropertyDescriptor(person1.__proto__, 'sex')
// {value: "boy", writable: true, enumerable: true, configurable: true}
复制代码

以上能够看出,咱们经过对象字面量和new运算符建立的对象的属性它们的writable,enumerable,configurable都有true,默认都是可写、可枚举、可配置。若是要修改属性的特性能够调用Object.defineProperty()

var obj = {
    name: 'sillywa'
}
// 将name属性设为不可枚举并将其值设为jack
Object.defineProperty(obj, 'name', {
    value: 'jack',
    enumerable: false
})
Object.getOwnPropertyDescriptor(obj, 'name')
// {value: "jack", writable: true, enumerable: false, configurable: true}

// 新增age属性
Object.defineProperty(obj, 'age', {
    value: 12
})
Object.getOwnPropertyDescriptor(obj, 'age')
// {value: 12, writable: false, enumerable: false, configurable: false}

// 将name变为存储器属性
Object.defineProperty(obj, 'name', {
    get: function () {
        return 0
    }
})
Object.getOwnPropertyDescriptor(obj, 'name')
// {set: undefined, enumerable: false, configurable: true, get: ƒ}

obj.age = 78
obj.age  // 12
复制代码

须要注意的是经过Object.defineProperty() 建立的属性其writable, enumerable, configurable 都为false。尝试修改不写的属性不会报错,但也不会修改,只有在严格模式下才会报错。

若是须要同时修改和建立多个属性,可使用Object.defineProperties()

var obj = Object.defineProperties({},{
    name: {
        value: 'sillywa',
        writable: true,
        enumerable: true,
        configurable: true
    },
    age: {
        get: function () {
            return 'hello' + this.name
        },
        set: function (value) {
            this.name = 'jack'
        },
        enumerable: true,
        configurable: true
    }
})
复制代码

4、属性的设置和屏蔽

咱们知道当咱们书写如下代码时

obj.foo = 'bar'

若是obj存在一个名为foo的普通数据访问属性,这条赋值语句只会修改已有的属性值。

若是foo不是直接存在于obj中,[[prototype]]链就会被遍历,若是原型链上找不到foofoo就直接被添加到obj上。

然而,若是原型链上找到了foo属性,状况就有些不同了。

若是属性foo既出如今obj中也在其原型链中,那么obj中包含的foo属性就会屏蔽原型链里面的foo属性,这就是属性屏蔽,原理就是属性的查找规则。

下面咱们看一下若是foo不直接存在于obj中,而是在其原型链中时,obj.foo = 'bar'会出现的三种状况:

  1. 若是原型链中存在名为foo的普通数据访问属性而且其writabletrue,那么就会直接在obj中添加foo属性,它是属性屏蔽。
  2. 若是原型链中存在foo,但其writablafalse,那么没法修改已有属性或者在obj中建立屏蔽属性。若是运行在严格模式下,会抛出一个错误。不然这条赋值语句会被忽略,不会发生属性屏蔽。
  3. 若是原型链上存在foo而且它是一个setter,那就必定会调用这个setterfoo不会被添加到obj中,也不会从新定义这个setter

大多数人认为,若是向原型链中已存在的属性赋值,就必定会发生属性屏蔽,但以上三种状况只有一种是如此。

若是但愿在任何状况下都屏蔽foo,那就不能使用=操做符来赋值,而是使用Object.defineProperty()来向obj中添加foo

状况一:

function Person() { }
Person.prototype.foo = 'foo'
var obj = new Person()
obj.foo = 'bar'
obj.foo   // 'bar'
复制代码

状况二:

function Person() {}
Object.defineProperty(Person.prototype,'foo',{
    writable: false,
    enumerable: true,
    configurable: true,
    value: 'foo'
})
var obj = new Person()
obj.foo = 'bar'
obj.foo  // 'foo'
复制代码

状况三:

function Person() {}
Person.prototype = {
    constructor: Person,
    name: 'foo',
    set foo (value) {
        this.name = value
    },
    get foo () {
        return this.name
    }
}
var obj = new Person()
obj.foo = 'bar'
obj.foo  // 'bar'
// obj中并无foo这个属性,只是调用了setter
obj.hasOwnProperty('foo') // false
复制代码

有些状况下会隐式产生屏蔽,必定要注意,思考一下代码:

var obj = {
    a: 2
}
var myObj = Object.create(obj)
obj.a  // 2
myObj.a  // 2

obj.hasOwnProperty('a')  // true
myObj.hasOwnProperty('a')  // false

myObj.a ++  // 隐式屏蔽

obj.a  // 2
myObj.a  // 3

myObj.hasOwnProperty('a')  // true
复制代码

尽管myObj.a ++看起来是查找并增长obj.a的属性,可是别忘了++操做符至关于myObj.a = myObj.a + 1;所以++操做首先会经过原型链查找到obj.a,并读取其值为2,而后加1赋值给myObj.a

相关文章
相关标签/搜索