小程序的六次改进

我们来做一个小程序,输入正方形边长为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自动做了四件事

  1. 自动创建空对象let obj = Object.create(squarePrototype)
  2. 自动给空对象关联原型,原型地址指定为X.prototypelet obj = Object.create(squarePrototype)
  3. 自动将空对象作为this关键字运行构造函数 obj.width = width
  4. 自动return thisreturn obj

此时构造函数的X作用是

  1. X函数本身负责给对象本身添加属性
  2. 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
		}
	}