对象创建
创建对象的方式有两种
第一种是使用对象字面量的方式,也就是声明形式
let obj = {
key: value
//...
}
第二种是使用构造方式
let obj = new Object()
obj.key = value
这两种方式虽然形式不同,但产生的是同一种对象
在实际应用中我们也会使用构造函数来生成实例对象
function Person(name) {
this.name = name
}
Person.prototype.say = function () {
return 'I am ' + this.name
}
let p = new Person('feng')
在ES6中,我们还可以利用class
关键字来创建对象
class Person {
constructor(name) {
this.name = name
}
say() {
return 'I am ' + this.name
}
}
typeof Person // "function"
Person === Person.prototype.constructor // true
let p = new Person('feng')
p.say === Person.prototype.say // true
我们可以把ES6的class
看做是一种语法糖,ES6的类作为构造函数的另一种写法。ES6的类与构造函数的主要区别在于:ES6类必须使用new
调用,而构造函数可以当作普通函数执行
属性
对象包含了存储在特定命名的位置上的任意类型的值,我们称这些值为属性。在对象中,属性名是字符串
let obj = {
a: 1 // 属性
}
访问在obj
对象中的a
,有两种方法
第一种是使用.
操作符,通常称为属性访问
,只能访问与标识符相同命名规范的属性
obj.a // 1
第二种是使用[]
操作符,通常称为键访问
,可以访问任何兼容 UTF-8/unicode 的字符串类型的属性名
let a = 'a'
obj['a'] // 1
obj[a] // 1
属性描述符
在JavaScript中,对象的属性用属性描述符来表示。属性有四种性质:值、可写性、可配置性、可枚举性
let obj = {
a: 1
}
Object.getOwnPropertyDescriptor( obj, "a" )
// { value: 1, writable: true, enumerable: true, configurable: true }
可写性
当writable
为false
时,属性变为不可写,也就意味着值不可改变
let obj = {}
Object.defineProperty( obj, "a", {
value: 1,
writable: false, // 不可写
configurable: true,
enumerable: true
} )
obj.a = 2
obj.a // 1
可配置性
当configurable
为false
时,就意味着我们不可以使用Object.defineProperty
来修改该属性的描述符定义,同时也无法使用delete
操作符对它进行删除
唯一例外的是,当configurable
为false
时,writeable
也可以从true
变为false
let obj = {
a: 1
}
Object.defineProperty( obj, "a", {
value: 2,
writable: true,
configurable: false,
enumerable: true
} )
Object.defineProperty( obj, "a", {
value: 3,
writable: true,
configurable: true,
enumerable: true
} ) // TypeError
Object.defineProperty( obj, "a", {
value: 3,
writable: false,
configurable: false,
enumerable: true
} )
obj.a // 3
delete obj.a
obj.a // 3
可枚举性
当enumerable
为false
时,就意味着这个属性不会在特定的枚举操作中出现
let obj = {
a: 1,
b: 2
}
Object.defineProperty( obj, "a", { enumerable: false } )
obj.a // 1
for (let k in obj) {
console.log( k, obj[k] )
} // b 2
setter和getter
利用取值函数和存值函数可以拦截指定属性的存取行为
let obj = {
b: 1,
set a(value) {
this.b = value*10
}
}
obj.a = 1
obj.b // 10
Object.defineProperty( obj, "c", {
get: function(){ return this.b * 2 },
enumerable: true
} )
obj.c // 20
原型
在JavaScript中只有对象,没有类
对象之间用原型联系起来
__proto__
与prototype
prototype
是构造函数的属性,而 __proto__
是对象的属性
每个对象的__proto__
属性指向自身构造函数的prototype
Object.prototype.__proto__ // null
Object.__proto__ === Function.prototype // true
Function.__proto__ === Function.prototype //true
let obj = {}
obj.__proto__=== Object.prototype // true
Function.prototype // [Function]
Function.prototype.__proto__ === Object.prototype // true
function Foo() {}
Foo.__proto__ === Function.prototype //true
let f1 = new Foo()
f1.__proto__ === Foo.prototype // ture
虽然有一点不可思议,但是结合上述文字以及代码,我们可以得到如下结论:
- 原型链的最顶端是内建的
Object.prototype
,在向上思考就是哲学的范畴了 Object
对象是由Funtion
函数创建出来的Function
是一种对象,因为对象是由函数创建出来的,所以它是Function
函数创建出来的- 使用字面量的方式创建的对象是由
Object
对象创建的 Function.prototype
指向一个对象,并且这个对象是由Object
对象创建的- 一个自定义的函数是由
Function
函数创建的 - 构造函数可以创建对象
简单的说,对象以自己为模版可以创建更多的对象,而函数既能创建函数又能创建对象,同时函数也是一种对象
链接
原型链的机制是一种存在于一个对象上的内部链接,它指向一个其他对象
原型链使得代码的复用性增强,同时对象的的属性的访问变得复杂起来,
let obj = {
a: 1
}
let obj1 = Object.create( obj )
obj1.__proto__ === obj //true
obj1.a // 1
obj.b = 2
obj1.b // 2
Object.defineProperty( obj, "b", { writable: false } )
obj1.b = 3
obj1.b // 2
Object.defineProperty( obj, "c", {
get: function(){ return this.b * 2 },
enumerable: true
} )
obj1.c // 4
for (let k in obj1) {
console.log(k, obj1[k])
}
/*
a 1
b 2
c 4
*/
以obj
对象为模板,创建出了obj1
对象,或者说obj1
对象的原型是obj
对象。通过观察,我们可以推论,在对对象的属性进行访问时,先查找对象内部是否存在名称匹配的属性,如果不存在,就沿着原型链向上查找,直到找到匹配的属性,或者已经到达原型链的最顶端
构造器
构造器是什么不重要,重要的是它做了什么
function Foo() {}
Foo.prototype.constructor === Foo //true
function Bar() {}
Foo.prototype = new Bar()
Foo.prototype.constructor = Foo
let obj = new Foo()
obj // Foo{}
obj.__proto__.__proto__ === Bar.prototype // true
挖一个坑
由于javascript特有的原型机制,在实际项目中可能会遇到两种截然不同的设计方式,一种是传统的面向对象的设计方式,另一种则是我在《你不知道的JS》一书中所了解的面向委托的设计,读者感兴趣可以去自己去了解一下