23面向对象
对象定义
OO面向对象:ECMA-262 把对象定义为:“无序属性的集合,其属性可以包含基本值、对象或者函数。”严格来讲,这就相当于说对象是一组没有特定顺序的值。对象的每个属性或方法都有一个名字,而每个名字都映射到一个值。
对象属性
对象有两种属性:数据属性和访问器属性。
- 数据属性有:configurable,enumerable,writable,value
configurable:相当于一把关上就打不开的锁,值为true或false,默认为true。表示是否能对定义在对象上的属性进行修改,例如能否通过delete 删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性。锁关上时,如果原来可写,能够改成不可写,如果原来不可写,不能改成可写。
enumerable:值为true或false,默认为true。表示能否遍历对象上的某属性(能否通过for循环返回属性)
writable:值为true或false,默认为true。表示能否修改该属性的值。
value:属性的值。原来有值时,这个值就是新的值;如果原来没有值,则读取这个值。
- 访问器属性有:configurable,enumerable,get,set
configurable和enumerable与上同
get:在读取访问器属性时,会调用 getter 函数,这个函数负责返回有效的值,默认值为 undefined。
set:在写入访问器属性时,会调用setter 函数并传入新值,这个函数负责决定如何处理数据,默认值为 undefined。
使用Object.defineProperty()来定义一个对象的属性【详细的见之前写的对象的方法】
创建对象
基本方式:
//①对象字面量方式
const obj = {
nickname: 'xiaoMing',
age: 3
};
//②new构造函数
const obj1 = new Object({
nickname: 'xiaoGang',
age: 4
});
//③Object实例
const obj2 = Object({
nikename: 'xiaoHong',
age: 5
})
工厂模式
❓什么是工厂模式
工厂模式是软件工程领域一种广为人知的设计模式,这种模式抽象了创建具体对象的过程。
优势:减少代码量,减轻代码负担
弊端:没有解决对象识别的问题(即怎样知道一个对象的类型)
❓为什么会有工厂模式
解决创建对象时相同的属性写了很多遍,产生大量的重复代码。比如:
//以下三个人物,由于不同姓名与年龄就创建了三个对象
let aaa = {
nickname: '纳尔',
age: 3
};
let bbb = {
nickname: '佐伊',
age: 2
}
let ccc = {
nickname: '安妮',
age: 4
}
console.log(aaa, bbb, ccc);
❗工厂模式的使用
声明一个函数,用函数来封装一特定接口来创建对象
function creatPerson(nickname, age){
const person = {
nickname: nickname,
age: age,
introduce: function() {
console.log(`大家好,我是${this.nickname},今年${this.age}岁`)
}
}
return person;
}
const person1 = creatPerson('纳尔', 3);
const person2 = creatPerson('佐伊', 2);
const person3 = creatPerson('安妮', 4);
console.log(person1, person2, person3);
person1.introduce();
person2.introduce();
person3.introduce();
打印结果为:
构造函数模式
❓什么是构造函数模式
创建自定义的构造函数,从而定义自定义对象类型的属性和方法。
优点:减少代码量,可以看出对象的类型
📌特点
- 创建的是构造函数,这个函数名字开头必须大写(来区别其他函数)
- 使用this承接创建的对象(使用new会在函数执行时创建这个对象)
- 不要return返回值(使用new去执行一个构造函数的时候,如果自己renturn了简单数据类型,无效;return引用类型,会覆盖new创建的对象,永远不要自己return)
- 调用时必须使用new,否则报错
❗构造函数模式的使用
function Person(nickname, age){
this.nickname = nickname;
this.age = age;
this.introduce = function() {
console.log(`大家好,我是${this.nickname},今年${this.age}岁`)
}
}
const hero1 = new Person('纳尔', 3);
const hero2 = new Person('佐伊', 2);
const hero3 = new Person('安妮', 4);
console.log(hero1, hero2, hero3);
hero1.introduce();
hero2.introduce();
hero3.introduce();
打印结果为:
以上代码的含义是,3岁的纳尔,2岁的佐伊和4岁的安妮分别介绍自己,那么假如,我们要为安妮新加一个方法,让她召唤小熊,而其他人却没有这个方法,那么:
function callBear(){
console.log(`我是${this.nickname},我会召唤小熊`)
}
callBear.call(hero3);
可是这样的话,弊端又出现了,新建的callBear函数单纯的为Person构造函数实例化出来的对象安妮(hero3)服务,所以有了下边的原型模式。
原型模式
❓什么是原型
每一个function声明的函数身上都有prototype
这个属性。这个对象会成为被new出来的对象的原型__proto__
。
浏览器控制台打印对象或函数等类型的时候出现的__proto__
标志,__proto__
下保存着这种数据类型的共有方法(工具箱),我们可以直接调用。
比如说所有数组都有slice方法,这个slice方法就保存在__proto__
下面,我们可以直接调用,这就是数组原型里的方法。
而原型模式就是我们写构造函数,创造出有独特方法的对象,成为这种函数的共有方法,这样使用该构造函数实例化出来的对象都有权访问这里的方法。
这里展示一个函数的原型
function foo() {};
console.dir( foo );
❗原型模式的使用
第一种写法:
function Human() {};
Human.prototype.nickname = '人类';
Human.prototype.age = '3';
Human.prototype.favorColor = 'orange';
Human.prototype.introduce = function () {
console.log(`大家好,我是${this.nickname},今年${this.age}岁了,最喜欢${this.favorColor}色.`);
};
let human1 = new Human();
console.log(human1);
打印结果展示为:
这样的好处还有,在里边声明的函数在内存中也只存在一个,不像工厂模式和构造函数模式,他们每次调用introduce方法都在内存中增加了一个地址。
此外,human1的原型方法就是声明的构造函数Human()所写的方法。
console.log( human1.__proto__ === Human.prototype);//true
第二种写法:
function Human(nickname, age, favorcolor){
this.nickname = nickname;
this.age = age;
this.favorcolor = favorcolor;
}
Human.prototype = {
introduce: function() {
console.log(`hello,my name is ${this.nickname},${this.age}yeas old now`);
}
}
let human1 = new Human('纳尔', 3, 'pink');
console.log(human1);
打印为:
这种写法美中不足之处在于,它缺少了constructor属性,而第一种写法有这个属性。
除此之外,运用原型模式的写法,会发现和之前利用工厂模式和构造函数模式时,打印出函数属性后显示的颜色不同,偏紫色表示可以遍历到,而浅粉色表示不能遍历。
我们使用原型模式可以遍历到我们自己往构造函数添加的方法和属性,显然,有时我们利用的只是原型里的方法带来的效果,而不是将他遍历下来,例如:
function Human(nickname, age, favorcolor){
this.nickname = nickname;
this.age = age;
this.favorcolor = favorcolor;
}
Human.prototype = {
introduce: function() {
console.log(`hello,my name is ${this.nickname},${this.age}yeas old now`);
}
}
let human1 = new Human('纳尔', 3, 'pink');
console.log(human1);
const fragment1 = document.createDocumentFragment();
for(let key in human1){
const div = document.createElement('div');
div.innerText = `${key}: ${human1[key]}`;
fragment1.append(div);
}
document.body.append(fragment1);
此时,浏览器页面将会:
显然这不是我们想要的效果。
因此,需要把原型上的属性定义成不可枚举属性
Object.defineProperties( Human.prototype,{
introduce: {
value: function() {
console.log(`hello,my name is ${this.nickname},${this.age}yeas old now`);
enumerable: false;
writable: true;
}
}
})
定义成不可枚举属性
Object.defineProperties( Human.prototype,{
introduce: {
value: function() {
console.log(`hello,my name is ${this.nickname},${this.age}yeas old now`);
enumerable: false;
writable: true;
}
}
})
这样将不会再遍历introduce方法了。