面向对象和面向过程
面向对象和面向过程都是一种程序设计思想
面向过程:注重解决问题的步骤,分析问题需要的每一个步骤,实现函数依次调用
面向对象:将数据和处理数据的程序封装到对象中,作为一个整体来看待,更贴近事物的自然运行模式。
面向对象特性:抽象 继承 封装 多态
面向对象在大型项目中,使得代码的维护、迭代更方便,很多情况下用面向对象写程序,也会用到面向过程,有些时候是相互穿插的,并不是非此即彼的
JS中的对象创建
- 字面量方式创建 例如:
let obj = {
name: 'kyrie',
age: 21,
hobby: function(){
console.log('basketball')
}
}
2.构造函数
例如:
let obj = new Object();
obj.name = "kyrie"
obj.age = 20;
obj.hobby = function(){
console.log('basketball')
}
3.Object.create()
属性和方法会放在原型上
let obj = Object.create({
name: 'kyrie',
age: 21,
hobby(){
console.log('basketball')
}
});
console.log(obj)
打印出来的结果:
对象属性方法的调用
1.通过 obj.xxx(xxx代表对象的属性或者方法)来调用属性或方法
obj.name
obj.hobby()
2.通过中括号 如 Obj['name']
在这种方法中括号里可以放入变量,而第一种调用方法里点后面不能识别变量,只能当做字符串去识别
let obj = {
name: 'kyrie',
age: 21,
hobby: function(){
console.log('basketball')
}
}
let x = "name"
console.log(obj.name)
console.log(obj['name'])
console.log(obj[x])
console.log(obj.x)
输出结果:
tips: 如果要在定义对象属性的时候用变量作为下标,可以用中括号作为键名(括号里还可以做一些简单的运算)
let x = 'name'
let obj = {
[x]: 'kyrie',
age: 21,
hobby: function(){
console.log('basketball')
}
}
工厂模式
如果我要生成两个角色对象的话,我们一般直接这样
let Naruto = {
name: 'Naruto',
age: 18,
skill(){
console.log('螺旋丸')
}
}
let Sasuke = {
name: 'Sasuke',
age: 18,
skill(){
console.log('须佐能乎')
}
}
但是如果我们要生成10个甚至把整个木叶村的忍者都生成出来的话,我们难道要copy 10次或n次上面这样的代码吗
可以但没必要
我们可以发现他们都有很多共同的属性和方法,我们尝试把他们抽离出来
function Ninja(name, age, skill){
let obj = {}
obj.name = name;
obj.age = age;
obj.skill = function() {
console.log(skill)
};
return obj;
}
let Naruto = Ninja('Naruto', 18, '螺旋丸')
let Sasuke = Ninja('Sasuke', 18, '须佐能乎')
可以发现下面代码的结果和上面的结果是一样的,但是下面的方式解决了代码的冗余问题,提高了代码的复用性,下面这种方式就是工厂模式的一个简单的应用,就像工厂的流水线一样去加工,然后返回对象给你
new 运算符
定义:new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例。
他会做些什么事呢:
1.当代码执行到这一句时,会自动的去执行new后面的函数
2.执行函数后,会自动的创建一个空对象
3.设置该对象的constructor
4.把空对象和this绑定
5.如果没有写返还,会隐式的返还this
通过new运算符我们可以改写下上面工厂模式的代码:
function Ninja(name, age, skill){
//let obj = {}
this.name = name;
this.age = age;
this.skill = function() {
console.log(skill)
};
//return obj;
}
let Naruto = new Ninja('Naruto', 18, '螺旋丸')
console.log(Naruto)
输出结果:
new XXX() 这样的一个过程我们就把他称为实例化 Naruto就是一个实例
构造函数
上面代码中的Ninja函数其实就是构造函数,通常就用new来调用
构造函数的特点:
1.为了区分构造函数和普通函数,约定俗成函数名首字母用大写
2.函数中的this指向实例化对象(实例)
静态属性和方法
我们可以把构造函数看成是类,类是泛指是抽象,而实例对象是具体的某一个。
但是我们可以发现上面构造函数里的属性和方法都是和实例化对象有关系的,但是其实也是有属于我们类(构造函数)本身的属性和方法,我们把它称作静态属性和方法(或叫静态成员)
tips: 函数也是对象
添加静态成员:
Ninja.style = "cool"
Ninja.run = function() {
console.log('run')
}
原型 Prototype
Ninja(忍者)他会疾跑、扔飞镖、忍术,也会像普通人一样呼吸、走路等等,那这么说的话我们的构造函数还可以变成这样
function Ninja(name, age, skill){
this.name = name;
this.age = age;
this.run = function() {
console.log(run)
};
this.breathe = function() {
console.log(breathe)
};
}
let Naruto = new Ninja('Naruto', 18, '螺旋丸')
let Sasuke = new Ninja('Sasuke', 18, '螺旋丸')
我们可以发现Naruto和Sasuke对象中有很多一模一样的方法,那么这些方法是不是同一个方法呢,我们可以这样进行判断
console.log(Naruto.run === Sasuke.run)
// 因为每次实例化调用函数时,都会在内存里开辟新的地址 而对于高级类型来说,等于号比较的是指针地址,所以输出结果为false
要是实例化一个对象只能通过这样子进行的话,我要生成一百个忍者,那么就会在内存里开辟100个新的地址,这样显然会造成内存的严重浪费
JS给我们提供了一个公共的空间去存放相同的方法,就是原型Prototype
实际上每一个构造函数在实例化的时候都会有两部分构成,一部分是构造函数,另一部分就是公共空间原型
我们把构造函数上的公共方法放在原型上
Ninja.prototype.run = function() {
console.log(run)
};
Ninja.prototype.breathe = function() {
console.log(breathe)
};
我们再来判断
console.log(Naruto.run === Sasuke.run)
// 引用的是同一个地址 所以输出结果为true
我们在每次声明一个构造函数时,都会自动帮我们声明一个原型,原型里面的this也是指向实例化对象,所以原型里面也可以拿到构造函数里的属性
约定俗成在构造函数里写上属性 在原型里写上方法
实例化对象的原型_proto_ 就等于构造函数的原型prototype
console.log(Naruto._proto_ === Ninja.prototype) // true
原型都有个固有属性constructor 会自动帮我们写好的,会指向构造函数
console.log(Ninja.prototype.constructor === Ninja) // true
console.log(Naruto.constructor === Ninja) // true
可以通过 xxx.prototype.xxx 去追加属性或方法,但是如果想要直接覆盖的话一定要记得加上constructor属性
Ninja.prototype = {
constructor: Ninja,
run: function() {
console.log(run)
},
breathe: function() {
console.log(breathe)
}
}
总结:构造函数可以说是两个部分构成,一是自身的属性和方法,二他的是原型prototype。 通过new 实例化后构造函数里的属性和方法指向object,实例的_proto_ 指向 prototype, prototype的constructor属性又指回构造函数
工厂模式对比构造函数:工厂模式没有原型,浪费内存;工厂模式没有构造函数原型里的constructor这样一个属性去判断对象通过哪个工厂模式函数来生成的
结尾
这是我的第一篇学习笔记,可能有一些不足,如有错误,欢迎指正,一起学习进步。