js面向对象探究

面向对象有三个特点,一个个来说

封装

私有变量

利用闭包实现对象的私有变量。

1
2
3
4
5
6
7
8
9
10
function Animal (age) {
this.getAge = function () {
return age
}
}
var dog = new Animal(3)
console.log(dog.age) // undefined
console.log(dog.getAge()) // 3

碰过的一个有趣的问题:
实现一个book构造函数,有一个属性id,每次调用该值加1。
运用闭包和立刻执行函数。

1
2
3
4
5
6
7
8
9
10
11
let Book = (function () {
let id = 1
return function () {
this.id = id++
}
})()
let bok1 = new Book()
let bok2 = new Book()
let bok3 = new Book()
console.log(bok3.id) // 3
共有方法

看下面的代码可以发现,getAge被重复创建了

1
2
3
var dog = new Animal(3)
var cat = new Animal(5)
console.log(dog.getAge === dog.getAge) // false

如果不想方法或者属性在每次new时新创建一份,可以将其设置在构造函数的原型prototype上。

1
2
3
4
Animal.prototype.feed = function () {
console.log('feed')
}
console.log(dog.feed === cat.feed) // true

继承

听说继承有六种方法,假设让Dog继承Animal,
,无非就是for in 复制属性,修改原型链如dog.prototype = new Animal,直接Object.create,在Dog中使用Animal.call
然而我们记住最好的一种就够了,就是组合继承。

先试下这样写,利用call的继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function Animal (name) {
this.name = name;
this.say = function() {
console.log(this.name)
}
}
// *1
function Dog (color, name) {
Animal.call(this, name)
this.color = color
}
// *2
let wangcai = new Dog('blue', 'wangcai')
console.log(wangcai) // {color: 'blue', name: 'wangcai')
wangcai.say() // 'wangcal'
// *3

可是如果在*1处加上这样的代码

1
2
3
Animal.prototype.say2 = function() {
console.log(this.name)
}

在*3处输入

wangcai.say2(),会报错提示不存在该方法,说明我们的继承是不完整的。dog没有继承原型链上的方法

我们需要在*2补上

1
2
Dog.prototype = Object.create(Animal.prototype)

这时候不会报错了,补上Object.create的polyfill

1
2
3
4
5
function objectCreate (proto) {
function F() {}
F.prototype = proto;
return new F();
}

然而还有一点小漏洞,当我们查看wangcai.constructor时,会发现指向的是Animal。因此我们需要修复一下

1
Dog.prototype.constructor = Dog

补充一下new的模拟

1
2
3
4
5
6
7
8
9
10
11
function fNew (base) {
var o = {}
o.__proto__ = base.prototype
base.call(o)
return o
}

完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function Animal (name) {
this.name = name;
this.say = function() {
console.log(this.name)
}
}
Animal.prototype.say2 = function () {
console.log(this.name)
}
function Dog (color, name) {
Animal.call(this, name)
this.color = color
}
Dog.prototype = Object.create(Animal.prototype)
Dog.prototype.constructor = Dog
let wangcai = new Dog('blue', 'wangcai')

检测继承是否成功的代码

1
2
3
4
5
6
7
8
console.log(wangcai instanceof Animal)
console.log(wangcai instanceof Dog)
console.log(wangcai.constructor === Dog)
console.log(wangcai.say2 === Animal.prototype.say2)
console.log(wangcai.__proto__ === Dog.prototype)
console.log(wangcai.__proto__.__proto__ === Animal.prototype)
console.log(wangcai.__proto__.__proto__.__proto__ === Object.prototype)
console.log(wangcai.constructor === Dog)

使用Object.create()和修复Dog.prototype.constructor = Dog是不是挺多余的?es6提供了这么一个函数Object.setPrototypeOf
因此我们可以使用
Object.setPrototypeOf(Dog.prototype, Animal.prototype)
替换刚刚提到的两行代码

可以了解到Object.setPrototypeOf(A,B)相当于令A.__proto__ = B

个人用的实现继承的代码
1
2
3
4
5
6
7
8
function extend(parent, fn) {
function child(...selfArg) {
parent.apply(this, selfArg)
fn && fn.apply(this, selfArg )
}
Object.setPrototypeOf(child.prototype, parent.prototype)
return child
}

多态

一个函数可以应用于不同的对象。并且根据this的不同,函数调用的结果也不同

1
2
3
4
5
6
7
8
9
10
11
12
function test() {
alert([this.a, this.b]);
}
test.call({a: 10, b: 20}); // 10, 20
test.call({a: 100, b: 200}); // 100, 200
var a = 1;
var b = 2;
test(); // 1, 2

或是在函数中检测arguments的数量和类型来实现多态

1
2
3
4
5
6
7
8
9
10
11
function add (a, b) {
if (arguments.length === 2) {
return a + b
} else {
return a + 1
}
}
console.log(add(1,4))
console.log(add(1))
支持作者

如果我的文章对你有帮助,欢迎 关注和 star 本博客 或是关注我的 github,获取更新通知。欢迎发送邮件到hpoenixf@foxmail.com与作者交流