面向对象
面向对象的语言有一个标志,它们都有类的概念,通过类可以创建任意多的具有相同属性和方法的对象。
什么是对象呢?
ECMA-262定义对象为: 无序属性的集合,其属性值可以包括基本值、对象或者函数。
属性类型
定义只有字啊内部采用的特性时,描述了属性的各种特征
为了表示特性是内部值,把它们放在两对方括号中,例如 [[Enumerable]]
ECMAScript包含两种属性: 数据属性 和 访问器属性
数据 属性
包含四个描述其行为的特性
[[Configurable]] 表示能否通过delete删除属性从而重新定义属性,能否修改属性的特性,
能否把属性修改为访问器属性,默认值为true
[[Enumrable]] : 能否 通过for-in循环返回属性, 默认为true
[[Writable]] 表示是否可以修改属性的值,默认为true
[[Value]] 包含属性的数据值 读取从这个位置 读 ;写入,把新值保存在这里 默认为undefined
要修改特性,必须使用Object.defineProperty()
方法
不可修改
var person = {};
Object.defineProperty(person, 'name', {
writable: false,
value: 'tom'
})
console.log(person.name) // tom
person.name = 'jerry'
console.log(person.name) // tom
访问器属性
不包含数据值 ,它们包含一对getter、setter,读取访问属性,会调用getter,写入属性,调用setter。
创建对象
工厂模式
function createPerson(name) {
var o = new Object()
o.name = name;
o.say = function () {
console.log(this.name)
}
return o
}
var p = createPerson('tom')
p.say()
构造函数模式
function Person(name) {
this.name = name;
this.say = function () {
console.log(this.name)
}
}
var p1 = new Person('tom')
p1.say()
var p2 = new Person('tom')
p2.say()
缺点 p1 p2上都有一个名为say的方法 每个属性 方法都要在每个实例上重新创建一遍。
原型模式
function Person() {
}
Person.prototype.name = 'tom'
Person.prototype.say = function () {
console.log(this.name)
}
var p = new Person()
p.say()
我们创建的每个函数都有一个prototype属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型所有实例共享的属性和方法。
只要创建一个函数, 就会为这个函数创建一个prototype属性,这个属性指向函数的原型对象,
而所有的原型对象都会获得一个 constructor,这个属性是指向prototype所在的函数的指针
例如上述例子中的 Person.prototype.constructor指向Person本身
总结:Person.prototype指向原型对象,而Person.prototype.constructor又指回了Person,实例内部__proto__仅仅指向原型对象,与构造函数没有直接关系,此外,虽然实例都不包含属性和方法,但仍然可以调用方法和属性,通过查找对象属性的过程(原型链)实现的
虽然在所有的实现中都无法访问到__proto__
,但是可以通过isPrototypeOf()
方法来确定对象之间是否存在这种关系
例如 console.log(Person.prototype.isPrototypeOf(p)) // true
ECMAScript58新增了一个getPrototypeOf()
的方法
console.log(Object.getPrototypeOf(p) === Person.prototype) // true
虽然可以通过对象实例访问原型中的值,但不能改变原型中值,如果我们给实例添加了一个与原型中属性同名的值。那么该属性会屏蔽掉原型中的那个同名属性
function Person() {
}
Person.prototype.name = 'tom'
Person.prototype.say = function () {
console.log(this.name)
}
var p = new Person()
p.say()
p.name = 'hello'
console.log(p.name) // hello
delete p.name
console.log(p.name) // tom
delete 之后可以重新访问到原型中name属性了
hasOwnProperty() 可以检测出一个属性是存在于实例还是原型中,存在于实例中才返回true
in 操作符
in操作符会在通过对象访问给定属性时返回true,无论该属性存在于实例还是原型中。
其他枚举方法包括 Object.keys()
更简单的原型写法
function Person() {
}
Person.prototype = {
name: 'tom',
say: function () {
console.log(this.name)
}
}
但是这样相当于重写了prototype,那么他的constructor不再指向Person了,如果这对你很重要,那么务必这样写
Person.prototype = {
constructor: Person,
name: 'tom',
say: function () {
console.log(this.name)
}
}
只不过这样会把他的内部特性,enumerable设置为true, 如果不想被枚举,那么使用Object.defineProperty()
吧
原型模式的缺点
一般来说 实例需要有属于自己的属性的,而原型会共享。
组合模式
function Person(name) {
this.name = name
}
Person.prototype.say = function () {
console.log(this.name)
}
var p = new Person('tom')
应用最广泛
寄生构造函数模式
这个模式除了需要加new 之外 和工厂模式其实一模一样
那么它存在的作用呢?
假如我们想创建一个具有额外方法的array,但我们不能直接修改Array这个构造函数,就可以使用这个模式
function SpecialArray() {
// 创建数组
var arr = new Array()
// 添加值
arr.push.apply(arr, arguments)
// 自定义方法
arr.myJoin = function () {
return this.join('|')
}
return arr
}
var a = new SpecialArray('1', '2', '3')
console.log(a.myJoin()) // 1|2|3
继承
原型链继承
首先回顾一下 构造函数、原型对象、实例 之间的关系。
每个构造函数都有它的原型对象 ,原型对象有都包含一个指向构造函数的指针,而实例又包含一个指向原型对象的指针。
那么假如我们让原型对象等于另一个对象的实例。显然此时原型对象将包含一个指向另一个原型的指针,这个原型也指向另一个的构造函数,假如这个原型也等于其他的实例,那么上述关系依然成立,层层递进,形成了原型链。
function SuperType() {
this.superVal = true
}
SuperType.prototype.getSuper = function () {
return this.superVal
}
function SubType() {
this.subVal = false
}
SubType.prototype = new SuperType(); // 继承
SubType.prototype.getSub = function () {
return this.subVal
}
var instance = new SubType();
console.log(instance.getSub(), instance.getSuper()) // false true
确定实例和原型的关系
1.通过 instanceof
方法
console.log(instance instanceof Object) // true
console.log(instance instanceof SubType)// true
console.log(instance instanceof SuperType)// true
console.log(SubType instanceof SuperType) // false
2.isPrototypeOf()
console.log(Object.prototype.isPrototypeOf(instance))// true
console.log(SuperType.prototype.isPrototypeOf(instance)) // true
console.log(SubType.prototype.isPrototypeOf(instance))// true
缺点 引用类型会被所有实例共享。
借用构造函数
组合继承
最常用
原型式继承
寄生式继承
寄生组合式继承