面向对象
面向对象是现实的抽象方法
-
对象是JavaScript中一个非常重要的概念,这是因为对象可以将多个相关联的数据封装到一起,更好的描述一个事物:
- Car: { 颜色:color, 价格:price, 品牌:brand }
-
用对象来描述事物,更有利于我们将现实的事物,抽离成代码中某个数据结构
-
JavaScript支持多种编程范式,包括函数式编程和面向对象编程:
- JavaScript中的对象被设计成一组属性的无序集合,像是一个哈希表,有key和value组成;
- key是一个标识符名称,value可以是任意类型,也可以是其他对象或者函数类型;
- 如果值value是一个函数,那么我们可以称之为是对象的方法;
-
创建对象的方式
// JavaScript // 1. var obj1 = new Object() obj1.name = "zzwi" obj1.age = 18 obj1.gender = "man" obj1.going = function() { console.log(this.name + "want to be a great person") } // 2. var obj2 = { name: "zzwi", age: 18, gender: "man", going: function() { console.log(this.name + "want to be a great person") } }
对属性操作的控制
delete
代码如下:
// JavaScript
// 承接上文代码
delete obj1.age // 删除对象obj1的age属性
console.log(obj1) // {name: 'zzwi', gender: 'man', going: ƒ}
// 可以看到,age属性确实被移除了
Object.defineProperty()
Object.defineProperty()方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
Object.defineProperty(obj,prop,descriptor)
-
可接收的三个参数:
- obj:要定义属性的对象
- prop:要定义或修改的属性的名称或Symbol
- descriptor:要定义或修改的属性描述符
-
返回值:
- 被传递给函数的对象
-
属性描述符的类型有两种:
- 数据属性描述符
- 存取属性描述符
configurable enumerable value writable get set 数据描述符 √ √ √ √ × × 存取描述符 √ √ × × √ √
configurable:
表示是否允许delete删除属性,是否可以修改属性值,是否可以将其(数据属性)修改为存取属性描述符
- 当我们直接在一个对象上定义某个属性时,这个属性的configurable为true
- 当我们通过属性描述符定义一个属性时,这个属性的configurable默认为false
承接上文代码
// JavaScript
Object.defineProperty(obj2,"age",{
configurable: false // 设置属性age不可更改
})
delete obj2.age
console.log(obj2) // {name: 'zzwi', age: 18, gender: 'man', going: ƒ}
// 属性age没有被删除
// JavaScript
// 如果这样敲代码则会报错
Object.defineProperty(obj2,"age",{
configurable: false
})
Object.defineProperty(obj2,"age",{
configurable: true
})
// 控制台报错
enumerable:
表示属性是否可以通过for-in或者Object.keys()返回该属性;
- 当我们直接在一个对象上定义某个属性时,这个属性的enumerable为true
- 当我们通过属性描述符定义一个属性时,这个属性的enumerable默认为false
代码承接上文
// JavaScript
Object.defineProperty(obj2,"age",{
configurable: false,
enumerable: false
})
console.log(Object.keys(obj2)) // ['name', 'gender', 'going']
writable:
表示是否可以修改属性的值;
- 当我们直接在一个对象上定义某个属性时,这个属性的writable为true
- 当我们通过属性描述符定义一个属性时,这个属性的writable默认为false
承接上文代码
// JavaScript
Object.defineProperty(obj2,"age",{
// configurable: false,
enumerable: false,
writable: false // 不允许修改该属性的值
})
obj2.age = 20
// delete obj2.age
console.log(obj2) // {name: 'zzwi', gender: 'man', age: 18, going: ƒ}
// 属性age的值无法修改
value:
属性的value值,读取属性时会返回该值,修改属性时,会对其进行修改;
- 默认情况下这个值是undefined
承接上文代码
// JavaScript
Object.defineProperty(obj2,"age",{
configurable: false,
enumerable: false,
writable: true, // 允许修改该属性的值
value: 20 // 将属性age的值修改为20
})
// delete obj2.age
console.log(obj2) // {name: 'zzwi', gender: 'man', age: 20, going: ƒ}
get:
获取属性时会执行的函数。默认为undefined
承接上文代码
// JavaScript
var address = "普宁市"
Object.defineProperty(obj2,"address",{
configurable: true,
enumerable: true,
get: function() {
return address
}
})
console.log(obj2.address) // 普宁市
set:
设置属性时会执行的函数。默认为undefined
承接上文代码
// JavaScript
var address = "普宁市"
Object.defineProperty(obj2,"address",{
configurable: true,
enumerable: true,
get: function() {
return address
},
set: function(value) {
address = value
}
})
console.log(obj2.address) // 普宁市
obj2.address = "广州市"
console.log(obj2.address) // 广州市
另外的写法:
// JavaScript
var obj = {
_age: 18, // 社区规范:_开头代表不希望被修改(私有属性)
running: function() {console.log("Running now")},
set age(value) {
this._age = value
},
get age() {
return this._age
}
}
console.log(obj.age) // 18
obj.age = 19
console.log(obj.age) // 19
这种写法默认属性操作符configurable
/ enumerable
的值都为true.
同时定义多个属性:
承接上文代码
// JavaScript
Object.defineProperties(obj1,{
name: {
writable: true,
value: "coder"
},
going: {
configurable: true,
value: "excellent"
}
})
console.log(obj1) // {name: 'coder', gender: 'man', going: 'excellent', age: 18}
注意:
value/writable 和 get/set 不共存
Object.defineProperty()的 get/set 便是vue2的响应式原理
Object.getOwnPropertyDescriptor
承接上文代码
// JavaScript
// 获取某一个特性属性的属性描述符
console.log(Object.getOwnPropertyDescriptor(obj1,"name"))
// 获取对象的所有属性描述符
console.log(Object.getOwnPropertyDescriptors(obj1))
对象内属性限制方法的补充:
// JavaScript
// 禁止对象继续添加新的属性
Object.preventExtensions(obj1)
obj1.address = "广州市"
console.log(obj1.address) // undefined
// JavaScript
// 禁止对象内的属性变化
Object.seal(obj1)
delete obj1.age
obj1.address = "广州市"
console.log(obj1.address) // undefined
console.log(obj1.age) // 18
// JavaScript
// 不允许对象内的属性值发生改变
Object.freeze(obj1)
obj1.age = 19
console.log(obj1.age) // 18
创建多个相似对象的方案
方案一:逐个创建
如下代码所示
// JavaScript
var p1 = {
name: '张三',
age: 18,
height: 1.60
};
var p2 = {
name: '李四',
age: 19,
height: 1.70
};
var p3 = {
name: '王五',
age: 20,
height: 1.80
};
...
缺点:创建同样的对象时,需要编写重复的代码
方案二:工厂模式
如下代码所示
// JavaScript
function createPerson(name,age,height,address) {
var p = {
name,
age,
height,
address,
eating: function() {
console.log(this.name+"在吃饭");
},
running: function() {
console.log(this.name+"在跑步");
}
}
return p
}
var v1 = createPerson("张三",18,1.60,"北京市")
var v2 = createPerson("李四",19,1.70,"上海市")
var v3 = createPerson("王五",20,1.80,"广州市")
...
缺点:打印创建对象时,对象的类型都是Object类型
方案三:构造函数
约定俗成的规范(非强制):构造函数首字母大写
JavaScript的构造函数和其他编程语言内的有点不太一样
- js的构造函数也是一个普通的函数,从表现形式来说,和千千万万个普通的函数没有任何区别;
- 如果有这么一个普通的函数被使用new操作符来调用了,那么这个函数就称之为是一个构造函数,并且它会执行如下操作:
- 在内存中创建一个新的对象(空对象)
- 这个对象内部的[[prototype]]属性会被赋值为该构造函数的prototype属性
- 构造函数内部的this,会指向创建出来的新对象
- 执行函数的内部代码(函数体代码)
- 如果构造函数没有返回非空对象,则返回创建出来的新对象
如下代码所示
// JavaScript
function Person(name,age,height,address) {
this.name = name
this.age = age
this.height = height
this.address = address
this.eating = function() {
console.log(this.name+"在吃饭");
}
this.running = function() {
console.log(this.name+"在跑步");
}
}
var q1 = new Person("张三",18,1.6,"北京市")
var q2 = new Person("李四",19,1.7,"上海市")
var q3 = new Person("王五",20,1.8,"广州市")
缺点:需要为每个对象的函数去创建一个函数对象实例
方案三补充:原型prototype
JavaScript中每个对象都有一个 [[prototype]] ,这个属性可以称之为对象的原型(隐式原型)
函数作为对象来说, 它也是有[[prototype]] 隐式原型
又因为它是一个函数, 所以它还会多出来一个显示原型属性: prototype
承接上文代码
// JavaScript
// 打印对象原型的方法
// 浏览器提供
console.log(obj.__proto__) // { ... }
// ES5之后的规范
console.log(Object.getPrototypeOf(obj)) // { ... }
当我们从一个对象中获取某一个属性时,它会触发[[get]]操作
- 在当前对象中去查找对应的属性,如果找到就直接使用
- 如果没有找到,那么会沿着当前对象的原型(链)去查找
// JavaScript
console.log(obj.hhh); // undefined
obj.__proto__.hhh = "哈哈哈"
console.log(obj.hhh); // 哈哈哈
函数作为对象来说, 它也是有[[prototype]] 隐式原型
又因为它是一个函数, 所以它还会多出来一个显示原型属性: prototype
// JavaScript
function Person() {}
// 函数原型
console.log(Person.prototype) // { ... }
var f1 = new Person()
var f2 = new Person()
console.log(f1.__proto__ === f2.__proto__) // true
console.log(f1.__proto__ === Person.prototype); // true
这个对象内部的[[prototype]]属性会被赋值为该构造函数的prototype属性
上方代码中,f1和f2的原型完全相同的原因是它们都指向同一个原型对象,即new操作后的构造函数Person所指向的原型对象prototype
从上图中,我们还可以看到
prototype.constructor = 构造函数本身
// JavaScript
console.log(Person.prototype.constructor); // ƒ Person() {}
console.log(Person.prototype.constructor === Person); // true
修改原型:
方法一:逐个更改
// JavaScript
Person.prototype.name = "coder"
Person.prototype.age = 18
console.log(f1.name,f1.age) // coder 18
方法二:修改整个对象
// JavaScript
Person.prototype = {
name: "zzwi",
age: "21",
gender: "man",
eating: function() {
console.log(this.name+"在吃饭");
},
running: function() {
console.log(this.name+"在跑步");
}
}
/* 因为属性constructor的属性描述符enumerable值为false,
即不可枚举,所以采用Object.defineProperty()进行添加*/
Object.defineProperty(Person,"constructor",{
configurable: true,
enumerable: false,
writable: true,
value: Person
})