**面向过程编程(Procedure Oriented Programming)**就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。
**面向对象编程(Object-Oriented Programming)**是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。
具体的实现我们看一下最经典的“把大象放冰箱”这个问题
以过程(步骤)为核心
第一步: 开门(冰箱); 第二步: 装进(冰箱,大象); 第三步: 关门(冰箱)。
// 开门的方法, todo是等待去实现的意思
function openDoor() {
// todo
}
// 装进去的方法
function putInto() {
// todo
}
// 关门的方法
function closeDoor() {
// todo
}
// step1
openDoor();
// step2
putInfo();
// step3
closeDoor();
以对象为核心
冰箱.开门() 冰箱.装进(大象) 冰箱.关门()
var iceBox = {
openDoor() {
// todo
},
putInto() {
// todo
},
closeDoor() {
// todo
},
};
iceBox.openDoor();
iceBox.putInto();
iceBox.closeDoor();
又比如办一个驾照:
要获得一个驾照, 你必须一个个部分去跑, 跑完所有流程, 都通过了就拿到了驾照
你也可以交钱一个相关公司, 他们派一个人帮你, 那个人熟悉没一个流程, 所以面向对象还有个好处是, 你根本不需要功能具体是怎么实现, 直接调用对象的方法就好了, 比如axios.js
我们主要掌握以下几点:
(1) 使用工厂模式创建对象#
用来创建功能类似的对象
function createPerson(name,age,job) {
// 创建一个空对象
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.say = function() {
console.log(o.name);
}
return o;
}
var p1 = createPerson('张三',20,'测试工程师');
var p2 = createPerson('李四',20,'前端工程师');
var p3 = createPerson('小芬',19,'UI工程师');
console.log(p1,p2,p3);
(2) 使用构造函数模式创建对象#
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = function() {
console.log(this.name);
}
}
var p1 = new Person("张三", 29, "测试工程师");
var p2 = new Person("李四", 28, "前端工程师");
p1.sayName();
p2.sayName();
问题1: 构造函数的this指向了谁
问题2: js中的new ()到底做了些什么
使用new来创建对象时, 系统"偷偷的"做了以下动作:
(3) 使用原型模式创建对象#
function Person() {
}
// prototype指向了Person原型(原型对象)
// 给prototype添加属性
Person.prototype.name = '中国人';
Person.prototype.type = '人类';
// 给prototype添加方法
Person.prototype.say = function() {
console.log(this.name);
}
var p = new Person();
console.log(p.type);
p.say();
// 指针, 引用, 内存地址, 属性 是一码事
原型对象的四个特点:
每一个构造函数都有一个prototype属性, 指向了函数的原型对象
比喻: 原型对象同时也是构造函数实例的隐形的"父亲"
实例继承原型对象所有的属性和方法
实例有一个内部指针 __proto__
指向了原型对象
Person.prototype === p1.__proto__;
原型对象有一个constructor属性指向了构造函数(了解)
Person.prototype.constructor === Person
function Person(name,age) {
this.name = name;
this.age = age;
}
Person.prototype.father = '老王';
Person.prototype.age = 30;
Person.prototype.getFather = function() {
console.log(this.father);
}
// p1,p2是Person"生产出来的",可以把Person看成是母亲,p1,p2则为儿子
var p1 = new Person('狗蛋',1);
var p2 = new Person('二胖',2);
console.log(p1);
console.log(p2);
图示:
__proto
指向了原型对象
多态是同一个行为具有多个不同表现形式或形态的能力。
多态的思想实际上是把“想做什么”和“谁去做“分开,多态的好处是什么呢?
多态的最根本好处在于,你不必再向对象询问“你是什么类型”而后根据得到的答案调用对象的某个行为,你只管调用该行为就是了,其他的一切多态机制都会为你安排妥当。
// 开始唱歌
function startSing(animal) {
animal.sing();
}
function Duck() { }
Duck.prototype.sing = function() {
console.log('嘎嘎嘎');
}
function Dog() { }
Dog.prototype.sing = function() {
console.log('汪汪汪');
}
var duck = new Duck();
var dog = new Dog();
startSing(duck);
startSing(dog);
// 人的构造函数
function Person() {}
Person.prototype.type = "人类";
Person.prototype.say = function () {
console.log("这是个人类");
};
// 继承: 让男人的原型对象等于Person的一个实例
Man.prototype = new Person();
function Man(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
var man = new Man("张三", 20, "男");
console.log(man);
// type和say都是继承而来
console.log('type',man.type);
man.say();
原型继承的缺点:
class
class Person{
// 构造器(其实就是es5里的构造函数)
constructor(nation) {
this.nation = nation;
}
say() {
console.log(`这是一个${this.nation}人`);
}
sing() {
console.log('人类会唱歌');
}
}
var p = new Person('中国');
console.log(p);
p.say();
p.sing();
实现继承
// es6的类, 用来创建对象
class Person{
// 构造器(其实就是es5里的构造函数)
constructor(nation) {
this.nation = nation;
this.type = '人类';
}
say() {
console.log(`这是一个${this.nation}人`);
}
sing() {
console.log('人类会唱歌');
}
}
// 定义一个女人的类
class Woman extends Person{
constructor(nation) {
// 调用父类构造器
super(nation);
this.sex = '女人';
}
}
var w = new Woman('中国');
console.log('国籍',w.nation);
console.log('类别',w.type);
w.sing();
1. 借用构造函数继承
function Father(name) {
this.name = name;
this.say = function () {
console.log("hello");
};
}
function Child(name) {
this.name = name;
// 调用Father方法
Father.call(this, name);
}
var p1 = new Child("张三");
p1.say();
优点:
缺点:
2. 组合继承(原型继承和借用构造函数继承的组合)
function Father(name, age) {
this.name = name;
this.age = age;
console.log(this);
}
Father.prototype.say = function() {
console.log('hello');
}
function Child(name,age) {
Father.call(this,name,age);
}
Child.prototype = new Father();
var child = new Child('Tom', 22);
console.log(child);
常用的继承方式唯一的缺点是,父类的构造函数会被调用两次
3. 寄生式继承
function createObj(o) {
var clone = object.create(o);
clone.sayName = function () {
console.log('hello');
}
return clone;
}
var father = {
name: '人',
nation: '中国'
}
var son = createObj(father);
// son继承了father,拥有father的属性,同时还拥有sayName的方法
console.log(son);
就是创建一个封装的函数来增强原有对象的属性,跟借用构造函数一样,每个实例都会创建一遍方法
4. 寄生组合式继承
es5最完美的继承方式
// 原型+借用构造函数+寄生
function Person() {
console.log(22);
this.class = '人类';
}
// 原型继承模式
Person.prototype.say = function() {
console.log(this.name);
}
/**
* 寄生 Man.prototype.__proto__ === Person.prototype;
* Man的父亲的父亲 === Person的父亲
*/
Man.prototype = Object.create(Person.prototype);
Man.prototype.constructor = Man;
function Man(name, age) {
this.name = name;
this.age = age;
// 借用构造函数模式
Person.call(this);
}
var man = new Man('张三丰', 100);
console.log(man);
这是es5最好的继承方式,集合了所有继承方式的优点于一身。
总结: 三种简单的继承方式
两种复杂的继承方式
TIP
设计模式
假设有一个空房间,我们要日复一日地往里 面放一些东西。最简单的办法当然是把这些东西 直接扔进去,但是时间久了,就会发现很难从这 个房子里找到自己想要的东西,要调整某几样东 西的位置也不容易。所以在房间里做一些柜子也 许是个更好的选择,虽然柜子会增加我们的成 本,但它可以在维护阶段为我们带来好处。使用 这些柜子存放东西的规则,或许就是一种模式。
function bookFatory(name, year, vs) {
var book = new Object();
book.name = name;
book.year = year;
book.vs = vs;
book.price = "暂无标价";
if (name === "JS高级编程") {
book.price = "79";
}
if (name === "css世界") {
book.price = "69";
}
if (name === "VUE权威指南") {
book.price = "89";
}
return book;
}
var book1 = bookFatory("JS高级编程", "2013", "第三版");
var book2 = bookFatory("ES6入门教程", "2017", "第六版");
var book3 = bookFatory("css世界", "2015", "第一版");
console.log(book1);
console.log(book2);
console.log(book3);
单例就是保证一个类只有一个实例,实现的方法一般是先判断实例存在与否,如果存在直接返回,如果不存在就创建了再返回,这就确保了一个类只有一个实例对象好处是可以减少不必要的内存开销
应用场景:
// 模拟数据库连接对象
发布---订阅模式又叫观察者模式,它定义了对象间的一种一对多的关系,让多个观察者对象同时监听某一个主题对象,当一个对象发生改变时,所有依赖于它的对象都将得到通知。
现实生活中的发布-订阅模式;
比如小红最近在淘宝网上看上一双鞋子,但是呢 联系到卖家后,才发现这双鞋卖光了,但是小红对这双鞋又非常喜欢,所以呢联系卖家,问卖家什么时候有货,卖家告诉她,要等一个星期后才有货,卖家告诉小红,要是你喜欢的话,你可以收藏我们的店铺,等有货的时候再通知你,所以小红收藏了此店铺,但与此同时,小明,小花等也喜欢这双鞋,也收藏了该店铺;等来货的时候就依次会通知他们;
在上面的故事中,可以看出是一个典型的发布订阅模式,卖家是属于发布者,小红,小明等属于订阅者,订阅该店铺,卖家作为发布者,当鞋子到了的时候,会依次通知小明,小红等,依次使用旺旺等工具给他们发布消息;
发布订阅模式的优点:#
1.支持简单的广播通信,当对象状态发生改变时,会自动通知已经订阅过的对象。 2.比如上面的列子,小明,小红不需要天天逛淘宝网看鞋子到了没有,在合适的时间点,发布者(卖家)来货了的时候,会通知该订阅者(小红,小明等人)。
对于第一点,我们日常工作中也经常使用到,比如我们的ajax请求,请求有成功(success)和失败(error)的回调函数,我们可以订阅ajax的success和error事件。我们并不关心对象在异步运行的状态,我们只关心success的时候或者error的时候我们要做点我们自己的事情就可以了~
例子1: js的事件就是发布-订阅模式#
用js实现发布-订阅#
class Event {
constructor () {}
// 首先定义一个事件容器,用来装事件数组(因为订阅者可以是多个)
handlers = {}
// 事件添加方法,参数有事件名和事件方法
addEventListener (type, handler) {
// 首先判断handlers内有没有type事件容器,没有则创建一个新数组容器
if (!(type in this.handlers)) {
this.handlers[type] = []
}
// 将事件存入
this.handlers[type].push(handler)
}
// 触发事件两个参数(事件名,参数)
dispatchEvent (type, ...params) {
// 若没有注册该事件则抛出错误
if (!(type in this.handlers)) {
return new Error('未注册该事件')
}
// 便利触发
this.handlers[type].forEach(handler => {
handler(...params)
})
}
// 事件移除参数(事件名,删除的事件,若无第二个参数则删除该事件的订阅和发布)
removeEventListener (type, handler) {
// 无效事件抛出
if (!(type in this.handlers)) {
return new Error('无效事件')
}
if (!handler) {
// 直接移除事件
delete this.handlers[type]
} else {
const idx = this.handlers[type].findIndex(ele => ele === handler)
// 抛出异常事件
if (idx === undefined) {
return new Error('无该绑定事件')
}
// 移除事件
this.handlers[type].splice(idx, 1)
if (this.handlers[type].length === 0) {
delete this.handlers[type]
}
}
}
}
var event = new Event() // 创建event实例
// 定义一个自定义事件:"load"
function load (params) {
console.log('load', params)
}
event.addEventListener('load', load)
// 再定义一个load事件
function load2 (params) {
console.log('load2', params)
}
event.addEventListener('load', load2)
// 触发该事件
event.dispatchEvent('load', 'load事件触发')
// 移除load2事件
event.removeEventListener('load', load2)
// 移除所有load事件
event.removeEventListener('load')
说出以下代码运行结果, 并解释为什么
var param = 1;
function main() {
console.log('1',param);
var param = 2;
console.log('2',this.param);
this.param = 3;
}
main();
var m = new main();
说出以下代码运行结果, 并解释为什么
知识点:
坑:
function Foo() {
getName = function() {
console.log('1');
};
return this;
}
Foo.getName = function() {
console.log('2');
}
Foo.prototype.getName = function() {
console.log('3');
}
var getName = function() {
console.log('4');
}
function getName() {
console.log('5');
}
Foo.getName();
getName();
Foo().getName();
getName();
new Foo.getName();
new Foo().getName();