小程序的六次改进
我们来做一个小程序,输入正方形边长为5,输出它的面积和周长。
1
2
3
4
5
6
7
8
9
10
|
//1.0版本
let square = {
width:5;
getArea(){
return this.width*this.width
},
getLength(){
return this.width*4
}
}
|
假如需要很多很多正方形呢?比如12个。大部分人会选择用循环搞定。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
//2.0版本
let squareList = []
for(let i = 0;i<12;i++){
squareList[i] = {
width:5;
getArea(){
return this.width*this.width
},
getLength(){
return this.width*4
}
}
}
|
如果不全是5呢?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
//3.0版本
let squareList = []
let widthList = [5,6,5,6,5,6,5,6,5,6,5,6]
for(let i=0;i<12;i++){
squareList[i] = {
width:widthList[i],
getArea(){
return this.width*this.width
},
getLength(){
return this.width*4
}
}
}
|
但这是垃圾代码,浪费了太多内存。getArea()
和getLength()
函数各自被重复创建了11次。
让我们借助原型,将12个对象的共有属性放到原型中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
//4.0版本
let squareList = []
let widthList = [5,6,5,6,5,6,5,6,5,6,5,6]
let squarePrototype = {
getArea(){
return this.width*this*width
},
getLength(){
return this.width*4
}
}
for(let i = 0;i<12;i++){
squreList[i] = Object.create(squarePrototype)//使用现有的对象来提供新创建的对象的__proto__。
squreList[i].width = widthList[i]
}
|
但是这个代码还是不够好。创建square
的代码过于分散。
所以我们尝试把代码抽离到一个函数中,然后调用这个函数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
//5.0版本
let squareList = []
let widthList = [5,6,5,6,5,6,5,6,5,6,5,6]
function createSquare(width){//此函数叫构造函数
let obj = Object.create(squarePrototype)
obj.width = width
return obj
}
let squarePrototype = {
getArea(){
return this.width*this*width
},
getLength(){
return this.width*4
}
}
for(let i=0;i<12;i++){
squareList[i] = createSquare(widthList[i])
}
|
最后一个缺点,squarePrototype
原型和createSquare
函数还是分散的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
//6.0版本
let squareList = []
let widthList = [5,6,5,6,5,6,5,6,5,6,5,6]
function createSquare(width){
let obj = Object.create(squarePrototype)
obj.width = width
return obj
}
createSquare.squarePrototype = {//把原型放到函数上,结合够紧密了吗?
getArea(){
return this.width * this.width
},
getLength(){
return this.width * 4
}
constructor: createSquare //方便通过原型找到构造函数
}
for(let i = 0; i<12; i++){
squareList[i] = createSquare(widthList[i])
console.log(squareList[i].constructor)
// constructor可以知道谁构造了这个对象:你妈是谁?
}
|
这段代码几近完美。
为什么不固定下来,让每个JS开发者直接使用呢?
new操作符
基于原型链
让我们最后用new写一次这个小程序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
let squareList = []
let widthList = [5,6,5,6,5,6,5,6,5,6,5,6]
function Square(width){
this.width = width
}
Square.prototype.getArea = function(){
return this.width*this.width
}
Square.prototype.getLength = function(){
return this.width*4
}
for(let i = 0;i<12;i++){
squareList[i] = new Square(widthList[i])
console.log(squareList[i].constructor)
}
|
这段代码没有一句多余的废话。
每个函数都有prototype属性,这是JS之父故意的。
每个prototype都有constructor属性,这也是JS之父故意的。
对比6.0版本,我们发现new X自动做了四件事
- 自动创建空对象
let obj = Object.create(squarePrototype)
- 自动给空对象关联原型,原型地址指定为X.prototype
let obj = Object.create(squarePrototype)
- 自动将空对象作为this关键字运行构造函数
obj.width = width
- 自动return this
return obj
此时构造函数的X作用是
- X函数本身负责给对象本身添加属性
- X.prototype对象负责保存对象的共用属性
基于class(ES6新语法)
1
2
3
4
5
6
7
8
9
|
function Square(width){
this.width = width
}
Square.prototype.getArea = function(){
return this.width*this.width
}
Square.prototype.getLength = function(){
return this.width*4
}
|
这是我们我们得出的最终版本代码。
然而非常遗憾的是,这个代码被某些前端认为是过时代码。
ES6引入了更易于理解的新语法,class统治了天下。
1
2
3
4
5
6
7
8
9
10
11
|
class Square{
constructor(width){
this.width = width
}
getArea(){
return this.width*this*width
}
getLength(){
return this.width*4
}
}
|