浅谈js面向对象
/**
* 浅谈js面向对象
* author: Mr Lee (James Lee)
*/
/* 一、创建一个类
创建一个类(具有相同属性和行为的对象的集合 */
const User = function (id, name, age) {
this.id = id
this.name = name
this.age = age
}
// User.prototype:原型对象相当于一个存储公共方法的仓库
User.prototype.getName = function () {
return this.name
}
User.prototype.setName = function (name) {
this.name = name
// 链式编程
return this
}
User.prototype.getAge = function () {
return this.age
}
User.prototype.setAge = function (age) {
this.age = age
return this
}
User.prototype.getId = function () {
return this.age
}
// 创建一个空对象,并改变this指向
var James = new User(520, ‘Mr Lee‘, 18)
console.log(James.getName(), James.getAge())
// 链式编程
James.setName(‘Miss Lee‘).setAge(16)
console.log(James)
console.log(‘*******************************************************‘)
/* 二、封装属性与方法 */
const UserA = function (id, name, age, hobby) {
this.id = id
this.name = name
this.age = age
// 私有属性 无法继承 私有静态属性:使用闭包
var hobby = hobby
// 私有方法
function setHobby(hobby) {
hobby = hobby
}
function getHobby() {
return hobby
}
this.getName = function () {
return name
}
this.setName = function (name) {
this.name = name
return this
}
this.getAge = function () {
return age
}
this.setAge = function (age) {
this.age = age
return this
}
}
// 静态公有属性
UserA.isChinese = true
// 静态公有方法
UserA.eat = function (food) {
console.log(‘I am eating‘ + food)
}
// 静态公有属性、方法存储区
UserA.prototype = {
// 新创建对象可以通过原型链访问到这些属性和方法
}
const JamesA = new UserA(520, ‘JamesA‘, 18, ‘code‘)
console.log(JamesA)
console.log(JamesA.getName(), JamesA.getAge())
JamesA.setName(‘Miss JamesA‘)
JamesA.setAge(16)
console.log(JamesA.getName(), JamesA.getAge())
// 静态公有属性,必须通过类名访问
console.log(JamesA.isChinese, UserA.eat(‘apple‘))
console.log(‘*******************************************************‘)
/* 三、使用闭包实现类的静态变量
在C语言中,静态变量 static, 存储在RAM的静态存储区 */
/* 立即执行函数:一个闭包
给老项目添加新功能时,可以使用闭包,避免对项目其他功能产生副作用
注意:闭包引用不释放,会导致内存泄漏;使用完UserB后置为null UserB = null */
const UserB = (function () {
// 静态私有变量
const isChinese = true
// 静态私有方法
function eat(food) {
console.log(‘I am eating‘ + food)
}
return function () {
this.id = id
this.name = name
this.age = age
/* ...... */
}
})()
/* ************************************************************** */
/* 四、通过一个安全的方式创建对象
通过类来创建对象时,可能会忘记使用new */
// new的作用:纠正this的指向,指向新创建的对象;否则就是——谁用谁知道
const UserC = function (id, name, age) {
this.id = id,
this.name = name,
this.age = age
}
const JamesC = UserC(110, ‘JamesC‘, 18)
// undefined this,谁用谁知道 严格模式执行undefined 否则指向window
console.log(JamesC)
// 怎样避免没有使用new 导致this指向异常?
const UserD = function (id, name, age) {
// 检测this指向是否异常,如果没使用new,那么this指向undefined或window
var hasNew = this instanceof UserD
if (hasNew) {
this.id = id
this.name = name
this.age = age
} else {
// 否则就New一个
return new UserD(id, name, age)
}
}
const JamesD = UserD(110, ‘JamesD‘, 18)
console.log(JamesD)
console.log(‘*******************************************************‘)
/* ************************************************************** */
/*
封装:隐藏底层的复杂性,提供一个统一的接口,降低模块之间的耦合性;隐藏数据,提高安全性
继承:方便代码复用,提高程序的可拓展性
多态性:接口复用
*/
/* 五、实现继承 */
/* 通过类来继承 */
// 子类的原型对象是父类的实例对象
// 构造函数自身没有的属性或方法会在原型链上向上查找
function Human() {
this.sleepTime = ‘8h‘
this.nature = [‘sleep‘, ‘eat‘, ‘lazy‘]
this.ability = function () {
console.log(‘make some tools‘)
}
}
const UserE = function (id, name, age) {
this.id = id
this.name = name
this.age = age
}
UserE.prototype = new Human()
const JamesE = new UserE(110, ‘JamesE‘, 18)
// 修改复杂类型产生副作用
JamesE.nature.push(‘handsome‘)
const BrainE = new UserE(110, ‘BrainE‘, 18)
// 通过原型链对象继承的属性与方法
console.log(JamesE.sleepTime)
JamesE.ability()
/*
注意:使用类继承,子类对父类中的复杂类型数据采用的是引用访问,
如果其中一个子类修改了引用类型的数据
那么将会对另外一个对象产生副作用
*/
console.log(BrainE.nature)
console.log(‘-------------------------------------------------------‘)
// 类继承的另一种形式
function inheritObj(obj) {
// 创建一个空函数对象
function Func() { }
Func.prototype = obj
return new Func()
}
var Person = {
nickname: ‘‘,
name: ‘‘,
age: 0,
hobby: [],
getHobby: function () {
console.log(this.hobby)
}
}
const personA = inheritObj(Person)
const PersonB = inheritObj(Person)
personA.nickname = ‘James‘
personA.name = ‘Mr Lee‘
personA.age = 20
var arr = [‘games‘, ‘music‘, ‘sports‘]
arr.forEach((item) => {
personA.hobby.push(item)
})
PersonB.name = ‘Brain‘
personA.getHobby()
// PersonB继承的复杂类型属性hobby跟PersonA的一致
PersonB.getHobby()
console.log(PersonB)
console.log(‘*******************************************************‘)
/* 通过构造函数继承 */
// 精华:call、apply
function HumanA() {
this.sleepTime = ‘8h‘
this.nature = [‘sleep‘, ‘eat‘, ‘lazy‘]
}
HumanA.prototype.ability = function () {
console.log(‘make some tools‘)
}
const UserF = function (id, name, age) {
// 在子类的构造函数环境中执行一次父类的构造函数实现
HumanA.call(this)
this.id = id
this.name = name
this.age = age
}
const JamesF = new UserF(112, ‘JamesF‘, 20)
JamesF.nature.push(‘handsome‘)
const BrainF = new UserF(112, ‘BrainF‘, 20)
console.log(JamesF)
console.log(BrainF)
console.log(‘*******************************************************‘)
/* error : 该继承方式没有涉及原型链prototype,所以子类无法继承父类原型方法
如果要继承该方法,就必须将方法放在构造函数中;但是这样创建出来的对象实例都会单独拥有一份方法,而不是共用 */
/* JamesF.ability() */
/* 为了解决上面遇到的问题,可以将类继承与构造器继承结合在一起 */
/* 组合继承 类继承 + 构造函数继承 */
function HumanB() {
this.sleepTime = ‘8h‘
this.nature = [‘sleep‘, ‘eat‘, ‘lazy‘]
}
HumanB.prototype.getNature = function () {
console.log(this.nature)
}
const UserG = function (id, name, age) {
// 构造函数继承 避免了继承父类复杂类型属性时,继承的是引用
HumanB.call(this)
this.id = id
this.name = name
this.age = age
}
// 类继承 可以访问到父类原型链上的方法
// 注意:这里会再次执行一遍父类构造
UserG.prototype = new HumanB()
UserG.prototype.ability = function () {
console.log(‘make some tools‘)
}
const JamesG = new UserG(10, ‘JamesG‘, 20)
const BrainG = new UserG(10, ‘BrainG‘, 20)
JamesG.nature.push(‘handsome‘)
// 可以访问父类原型链上的方法,同时修改继承下来的复杂类型属性,不会对其他实例对象造成影响
JamesG.getNature()
BrainG.getNature()
JamesG.ability()
/* 缺点:
1. 在使用构造函数继承的时候,执行了一遍父类构造函数;当实现子类的类继承时,又会再次执行一遍父类构造
2. 子类不是父类的实例,但是子类的原型是父类的实例
*/
console.log(‘*******************************************************‘)
/* 寄生式继承 */
// function inheritObj(obj) {
// // 创建一个空函数对象
// function Func() { }
// Func.prototype = obj
// return new Func()
// }
const PersonA = {
name: ‘‘,
age: 0,
gender: ‘male‘,
hobby: []
}
function createPerson(obj) {
// new一个新对象
var newObj = new inheritObj(obj)
newObj.getName = function () {
console.log(this.name)
return this
}
newObj.getAge = function () {
console.log(this.age)
return this
}
newObj.getHobby = function () {
console.log(this.hobby)
return this
}
return newObj
}
const JamesLee = createPerson(PersonA)
JamesLee.name = ‘JamesLee‘
JamesLee.hobby = [‘music‘, ‘sports‘]
const BrainChen = createPerson(PersonA)
BrainChen.name = ‘BrainChen‘
// 子类实例对象继承自父类的复杂类型属性不会相互影响
JamesLee.getName().getAge().getHobby()
BrainChen.getName().getAge().getHobby()
console.log(‘*******************************************************‘)
/* 终极继承方式:寄生 + 组合 */
const UserH = function (id, name, age) {
this.id = id
this.name = name
this.age = age
}
// 父类原型方法
UserH.prototype.getName = function () {
console.log(this.name)
}
const PersonH = function (id, name, age) {
// 构造函数继承 避免复杂类型属性继承的是引用
UserH.call(this, id, name, age)
// 子类拓展属性
this.hobby = []
}
/*
通过组合继承,无法继承父类的原型对象,毕竟不是通过prototype
而且组合继承有个缺点:就是父类的构造函数在子类的构造函数中执行一次外,还需要在类继承的时候再次调用一次
但是只需要 继承父类的原型对象 即可,没必要去调用两次
*/
function inheritPrototype(sub, sup) {
// 创建一个临时对象,继承父类的原型对象
var temp = inheritObj(sup.prototype)
// 纠正临时对象构造器指向
temp.constructor = sub
// 设置子类的原型
sub.prototype = temp
}
// 子类继承父类的原型对象
inheritPrototype(PersonH, UserH)
// 子类在原型对象上新增方法
PersonH.prototype.getHobby = function () {
console.log(this.hobby)
return this
}
const JamesH = new PersonH(111, ‘JamesH‘, 18)
const BrainH = new PersonH(110, ‘BrainH‘, 16)
JamesH.hobby = [‘music‘, ‘game‘]
JamesH.getHobby().getName()
BrainH.hobby = [‘sports‘]
BrainH.getHobby().getName()
console.log(‘*******************************************************‘)
/* 六、实现多继承 java不支持,c++支持*/
// javascript仅一条原型链,理论上不支持多继承,但是可以向java一样通过多重继承来实现多继承
// 实现一个单继承 ------属性复制(浅拷贝)
// Object.assign(target, source)
const extend = function (target, source) {
for (let property in source) {
target[property] = source[property]
}
return target
}
const JamesI = {
name: ‘james‘,
age: 18,
gender: ‘male‘
}
const BrainI = {}
const res = extend(BrainI, JamesI)
console.log(res)
// 这种复制仅对简单类型有效,复杂类型拷贝的是引用
console.log(‘*******************************************************‘)
// 单继承------属性复制(深拷贝)
const extendX = function (target, source) {
for (let property in source) {
// 判断属性类型
const type = Object.prototype.toString.call(source[property])
if (type === ‘[object Array]‘) {
target[property] = []
source[property].forEach(i => target[property].push(i))
} else if (type === ‘[object Object]‘) {
target[property] = {}
extendX(target[property], source[property])
}
target[property] = source[property]
}
return target
}
const JamesJ = {
name: ‘Mr Lee‘,
nickname: ‘James Lee‘,
hobby: [‘music‘, ‘sports‘],
grade: {
math: 100,
english: 100,
}
}
const BrainJ = {}
const resX = extendX(BrainJ, JamesJ)
console.log(resX)
console.log(‘*******************************************************‘)
/* 实现多继承--------拷贝多个对象属性 */
const multiInherit = function() {
let length = arguments.length
let target = arguments[0]
let source
for(let i=1; i<length; i++) {
source = arguments[i]
extendX(target, source)
}
return target
}
// Object.prototype.multiInherit = multiInherit
const brand = {
brandName: ‘‘,
brandValue: ‘‘,
}
const appearance = {
height: ‘‘,
width: ‘‘,
color: [‘red‘,‘green‘,‘pink‘]
}
const car = {
price: ‘‘,
manufacturer: ‘‘
}
const tesla = {}
multiInherit(tesla,car,appearance,brand)
console.log(tesla)
console.log(‘*******************************************************‘)
/* 实现多态性-------一个方法的多种调用方式*/
const add = function() {
var num = arguments.length
const pickUp = {
0: zero,
1: one,
2: two
}
function zero() {
return 0
}
function one() {
return 1
}
function two() {
return 2
}
return pickUp[num]()
}
// 参数类型或数量不同,返回的结果就不同
console.log(add(),add(‘‘),add(‘‘,‘‘))
浅谈js面向对象的写法