面向过程
面向过程就是我们再开发或者说在生活中遇到一些事情的时候,按照需求去做,遇到了就解决,列举去问题一步一步的去实现
举例:现在有一台挖土机,它需要挖土
// 挖土机要挖土
function excavatorMove(){//...挖土机移动的逻辑}
function excavation(){//...挖土机挖土的逻辑}
function complete(){//...挖土机挖土完成的逻辑}
function render(){// 重新加载}
//第一步移动
excavatorMove()
render()
//第二步挖土
excavation()
render()
//第三步完成收工
complete()
render()
像这样一步一步的去完成我们需要的功能,就叫做面向过程,其实和面向函数是差不多的意思,面向函数就是将各种动作进一步封装成工具,就像项目中的utils文件一样,都已一个一个的功能函数。
对比于面向对象的优缺点:
优点 | 缺点 |
---|---|
每一步都要重新执行对应的逻辑代码,不利于维护、复用和扩展 | 步骤紧密,每一步都独立执行,性能上会更好 |
面向对象(面试重点呀)
先来概括一下面向对象编程:全称(Object Oriented Programming 面向对象的程序设计),意思就是将想法拟成一个实在的对象的变成范式,就是以问题来划分功能,将功能具象化到对象上,或者说使得对象具备某种能力。
举例:
一台挖土机,它还需要挖土。
那么在挖土机干活的时候会有几个步骤;
1.挖土机移动
2.挖土
3.再回来
// 挖土机挖土
// 这里不想太啰嗦,就假设Excavator封装了各种挖土机的执行操作 renderScene负责渲染
const excavator = new Excavator('挖土机一号')
const renderScene= new RenderScene()
// 第一步 挖土机移动
excavator.move()
renderScene.render()
// 第二步 挖土
excavator.excavation()
renderScene.render()
// 第三步 完成
excavator.complete()
renderScene.render()
这里会感觉这跟面向过程差不多的呀
那多追加一步操作,挖土一铲子倒回去半铲子
// 面向过程每次的excavation都要追加倒回
excavatorMove()
refund()
render()
// ...一系列回倒操作
// 面向对象
excavator.refund()
renderScene.refund()
renderScene.render()
这样来讲面向对象就是将能力赋予一个一个的对象,然后分工合作来完成所需,并且这些能力是可扩展的
对比于面向对象的优缺点:
优点 | 缺点 |
---|---|
易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统 更加灵活、更加易于维护 | 性能比面向过程低 |
… | 为什么说面向对象性能低,这样理解,因为比面向过程多了一层继承包裹,多一步访问操作肯定比直接就是干委婉一点,我是这样理解的 |
js中的面向对象
创建方法
var car = {}
var car = Object.create({})
var car = new Car()
var car ={} 和 var car = Object.create({})什么区别呢
对比起来 Object.create({})多了一层Prototype
// An highlighted block
var car = {}
var carz = Object.create({})
console.log(car.__proto__.constructor == carz.__proto__.__proto__.constructor)
// 打印结果 true
// 对比起来其实就是多了一层构造
至于new关键字,就是创建出对应的实例
重点来了,面试必备 手动实现new
先看new干了什么事情
- 创建一个对象
- 对象的原型指向构造函数的prototype
- 该对象实现了上层构造函数的方法
- 根据对应的情况返回
如果构造函数没有返回,或者返回了非对象,那么new之后就返回创建的对象;
如果构造函数明确返回了一个对象,那么new之后就返回那个对象;
function myNew(foo,...rest){
if(typeof foo !== 'function'){
throw new Error('目标不是一个函数,目标需要一个构造函数入参')
}
// 创建一个对象对象的原型指向构造函数的prototype
const obj = Object(foo.prototype)
// 或者 是const obj = {} obj.__proto__ = foo.prototype赋值新的原型
// 在对象上实现上层构造函数的方法(通过指向改变,将构造函数中的逻辑执行一遍)
const result = foo.call(obj,rest)
// 根据情况返回对应的值
return result&&typeof result == 'Object'?result:obj
}
原型
什么是原型,prototype,这个就是原型,他的概念就是被实例化的对象在访问属性的时候,如果本身没有挂载对应的属性那么就会去原型上找,原型的本质也是一个对象,可以理解为一个指针指向了某一个对象,每个对象都会有这样的指针,从而产生可扩展可继承性。
// 简单示例
var obj = {}
console.log(obj.__proto__)
// 输出 {constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …}
// obj.__proto__就指向它的原型对象,对象的方法都在原型上可以找到。
// 数组对象、字符串对象、数字对象都一样。
(比喻一下)可以说原型就是对象的父级,对象被构造函数创建出之后会携带原型上的属性,构造函数和原型合作支撑了对象的产生和使用。
原型链
对象的基础是构造函数创建,实例对象通过__proto__关联构造函数的原型,构造函数通过prototype关联原型,原型通过constructor关联构造函数,相互联系。
原型链是对象通过__proto__指向原型对象,原型对象又可以通过__proto__指向自己的原型一直往上查询到null,这就搭建了原型链。
为什么这么设计?
function Per(name){
this.defined = '人'
this.name = name
}
Per.prototype.method1 = function (){console.log('我会coding')}
var p = new Per('大老王')
var p2 = new Per('小王')
p2.method1 = function (){console.log('我会很秀的coding')}
p2.sex = '外星人'
- p和p2拥有Per上的能力,并且继承了prototype的coding能力,不用再去反复的创建;
- p2新增一些Per没有的能力,有良好的扩展性;
通常记下就ok,灵活运用起来的特点就是扩展性,复用性,可维护性
重中之中-继承(面试重点)
前端的继承方式
- 构造函数继承;
- prototype原型继承;
- class继承;
- call、apply、bind寄生式继承;
基础是这些,也可以混合使用
原型继承
function Per(sex){
this.sex = sex
this.list = [1,2]
}
Per.prototype.say = function (){console.log('你是谁')}
function p(){}
// 修改原型指向
p.prototype = new Per('男')
// 指回构造器
p.prototype.constructor = p
var son = new p()
var son2 = new p()
son.list.pop()
console.log(son2.list) //输出[1]
为什么要修改constructor?
不修正的时候constructor指向的是Per,那么在使用构造器的时候正常应该是访问到p,但是访问到的却不是p而是Per
实例化的时候无法传参数到被继承方,被继承方有引用类型的属性,修改时候会影响所有
构造函数继承
function Per(sex){
this.sex = sex
this.list = [1,2]
this.say2= function(){console.log('你是那个')}
}
Per.prototype.say = function (){console.log('你是谁')}
function p(sex){
Per.call(this,sex)
}
var son = new p('女')
var son2 = new p('男')
属性独立,无法继承原型上的属性,方法都在构造函数中定义,每次创建实例都会创建一遍方法
组合继承
function Per(sex){
this.sex = sex
this.list = [1,2]
}
Per.prototype.say = function (){console.log('你是谁')}
function p(sex){
Per.call(this,sex)
}
p.prototype = new Per()
p.prototype.constructor = p
const son = new p()
可以继承原型属性也可以继承静态属性,但会调用两次父级构造函数
组合寄生继承
function Per(sex){
this.sex = sex
this.list = [1,2]
}
Per.prototype.say = function (){console.log('你是谁')}
function p(sex){
Per.call(this,sex)
}
p.prototype = Object.create(Per.prototype)
p.prototype.constructor = p
可继承静态和原型属性,避免属性共享
class继承 直接用
class XXX extends xxx {}
class继承和组合寄生的区别
- class继承继承静态属性;
- class子类中的constructor中必须调用super,因为子类的this要通过父类的构造函数去构建;