JavaScript
常见的设计模式
设计模式 | 描述 |
---|---|
单例模式 | 保证一个类仅有一个实例,并提供一个访问它的全局访问点 |
工厂模式 | |
发布订阅模式 | |
观察者模式 | |
发布订阅模式 (观察者模式)
- 一种对象间一对多的依赖关系
- 当一个对象的状态发送改变时,所有依赖它的对象都得到状态改变的通知
订阅+发布过程
把自己想订阅的事件注册 (subscribe) 到调度中心 (event channel)
发布者 (publisher) 发布该事件到 (publish event) 到调度中心
事件触发时,调度中心统一调度订阅者注册到调度中心的处理代码
问题
- 订阅者是如何订阅?
调用事件对象中的注册事件,将自己事件名和执行函数,注册到注册中心中。然后出发事件发布即可执行订阅上的事件代码块
创建对象-工厂模式
普通方式,字面量方式,代码量比较多
优点:一个函数,即可创建对象
- 只要一个函数,可多次调用
缺点:
- 没有解决对象识别 (知道一个对象的类型??)
- 返回默认的,如果不处理则会返回固定的
js
function factory(name, age, job) {
// const obj=Object.create({})//带有普通对象的__proto__ 类似 const obj = new Object()
// const obj=Object.create(null)//则没有_proto__!
const obj = {};
obj.age = age;
obj.name = name;
obj.job = job;
obj.sayName = function () {
return this.name;
};
return obj;
}
//use
const p = factory('张三', '28', '前端狗');
创建对象-构造函数
优点:
- 没有显示地创建对象
- 直接将方法和属性赋值给 this 对象
- 没有 return
缺点:
- 每个方法都要在每个实例上重新创建一边,
p1.sayName===p2.sayName
同样任务,但两遍,两者不等于,证明这一点特点:
- 大写构造函数首字母,惯例
- 它的实例都有一个
constructor
(构造函数) 属性,指向他的构造函数
js
function ConstructorFn(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = function () {
return this.name;
};
}
// use
const p1 = new ConstructorFn('李四6i7', '29', '后端喵');
// 监测类型
console.log(p1 instanceof ConstructorFn); //true,同样都是Object的实例
当做构造函数,见上面
当普通函数
js
ConstructorFn('李四6i7', '29', '后端喵');
window.sayName();
在另外一个对象的作用域中调用
js
const ob = {};
ConstructorFn.call(ob, '王五', '30', 'python');
// ConstructorFn.apply(ob,["王五",'30','python']) 或者这样
on.sayName();
缺点 (原型模式解决):
- 在全局作用域下声明函数,只能被某个对象调用,意思是,专属的函数
- 而如果需要定义多个函数的,那么你需要声明多个函数。。。
- 如何对构造函数进行优化呢?属性和函数定义区分开,
js
function ConstructorFn(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = sayName;
}
function sayName() {
return this.name;
}
const p1 = new ConstructorFn('孙六', '31', '产品汪');
创建对象-原型模式
特点:
- 所有实例都共享原型的属性和方法
优点:
- 修改单一个实例,不会影响到其他实例
缺点:
js
function Proto() {}
Proto.prototype.name = '刘七';
Proto.prototype.age = '32';
Proto.prototype.job = '设计狮';
Proto.prototype.sayName = function () {
return this.name;
};
var p1 = new Proto();
var p2 = new Proto();
p1.sayName === p2.sayName; //true
理解原型对象
- 默认情况,原型对象自动取得 constructor 属性,其他方法和属性都是从 Object 继承
- 使用
Person.isPrototypeOf()
测试实例是否有一个纸箱构造函数prototype
的指针 hasOwnProperty()
访问的值是不是实例的属性,该方法会忽略从原型链继承到的属性Object.getOwnPrototypeDescriptor()
用于实例属性原型与 in 操作符
"name" in p1
查找该实例上的属性,不管是实例上还是原型上
- IE 早期版本出现 bug,导致无法被
in
出来,所以替代的方案是Object.keys()
,可列出可枚举的字符串数组更简单的原型语法,
- [x] 字面量包装
prototype
,但!constructor
没有指向构造函数了
js
function Proto() {}
Proto.prototype = {
name: 'xx',
age: '44',
job: 'ceo',
sayName() {
return this.name;
},
};
- [x] 字面量包装
prototype
,初始化回来constructor
。!但是,此时,constructor
是可以被枚举的。
js
function Proto() {}
Proto.prototype = {
constructor: Proto, //重新指向
name: 'xx',
age: '44',
job: 'ceo',
sayName() {
return this.name;
},
};
3。[√] 所以只能用 es5 的,Object.defineProperty()
js
function Proto() {}
Proto.prototype = {
name: 'xx',
age: '44',
job: 'ceo',
sayName() {
return this.name;
},
};
/*只允许在支持es5 Object.defineProperty()方法的环境下使用这样的方式*/
Object.defineProperty(Person.prototype, 'constructor', {
enumerable: false,
value: Person,
});
原型的动态性实例中的指针仅指向原型,并不是指向构造函数,当时
new 出来 的prototype
,即最初原型
,以下代码说明这一点:
js
function Proto() {}
var p1 = new Proto();
Proto.prototype = {
name: 'xx',
age: '44',
job: 'ceo',
sayName() {
return this.name;
},
};
p1;
原生对象的原型
- 给原型对象,添加方法,再
new
出来原型对象的问题
- 忽略构造函数传递初始化参数
- 所有实例获取相同的属性值
共享
的本质
js
function Proto() {}
Proto.prototype = {
name: 'xx',
age: '44',
job: 'ceo',
test: ['men', 'women'],
sayName() {
return this.name;
},
};
var p1 = new Proto();
var p2 = new Proto();
p1.test.push('son');
// 此时
console.info(p1.test === p2.test); //true