第一章 函数的几种编写方式
1.1 函数是变量
注意以下代码中的script部分,checkName1 和 checkName2 都是将函数保存在变量里来实现功能,通过控制台可以发现他们都是全局对象window下的一个变量,即全局变量。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
function checkName1(){
console.log('checkName1 !')
}
checkName1()
var checkName2 = function(){
console.log('checkName2 !')
}
checkName2()
console.log(this)
</script>
</body>
</html>
1.2 用对象收编变量
上面说了函数是保存在变量里面来实现功能,即声明了全局变量,但是如果是团队开发的话,就不能够直接声明全局变量函数,因为别人也可能定义了相同的方法,这样就会和你的函数相互覆盖,而且这种问题很难察觉。因此我们可以通过创建一个对象,然后将函数作为对象属性的方式收编,这样就可以大大降低相互覆盖的可能性了。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
// 用对象收编变量
var checkObject1 = {
checkName: function(){
console.log('checkName1')
},
checkEmail: function(){},
checkPassword: function(){}
}
// 对象的另一种形式,函数也是对象,因此也可以通过 . 的方式收编变量
var checkObject2 = function(){}
checkObject2.checkName = function(){
console.log('checkName2')
}
checkObject2.checkEmail = function(){}
checkObject2.checkPassword = function(){}
// 使用
console.log(checkObject1)
console.log(checkObject2)
checkObject1.checkName()
checkObject2.checkName()
</script>
</body>
</html>
从上面的代码和控制台输出可以看到,函数也是一个对象,虽然输出的时候无法看到收编的变量,但是通过 . 仍然能够使用。
提示:函数是对象,对象的属性也可以是对象,理论上来说,只要你愿意,可以无限嵌套。
1.3 通过return返回的对象都是一个新的对象
上面我们已经可以通过对象收编变量了,但是如果很多人都想要这些变量,可是对象只有一个,那怎么办呢?
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
var checkObject = function(){
return {
checkName: function(){
console.log('checkName')
},
checkEmail: function(){},
checkPassword: function(){}
}
}
var a = checkObject()
var b = checkObject()
console.log('a === b',a === b)
a.checkName()
b.checkName()
console.log('a.checkName === b.checkName',a.checkName === b.checkName)
a.checkName = function(){
console.log('new checkName')
}
a.checkName()
b.checkName()
</script>
</body>
</html>
使用函数的方式返回对象时,虽然看上去都是返回了checkObject 对象,但是实际上返回的每次都是一个新的对象。
虽然对象中的方法执行的都是相同的语句,但是值并不同,并且修改其中一个对象的方法(比如a.checkName)不会影响到另外一个对象中的方法(比如b.checkName)
总结:每次通过 return 返回的对象都是一个新的对象,就像是克隆体,虽然长得一模一样,但是都是相互独立的个体。
1.4 类说:“俺也一样”
虽然通过函数return的方式可以批发对象,完成我们的需求了,但是这毕竟不是一个真正意义上创建类的方式,并且创建的对象a、b和checkObject没有任何关系(使用人突然return 返回的对象本生就和checkObject无关),所以接下来我们要改造一下。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
var checkObject = function(){
this.checkName = function(){
console.log('checkName')
}
this.checkEmai = function(){}
this.chekPassword = function(){}
}
var a = new checkObject()
var b = new checkObject()
console.log('a === b',a === b)
a.checkName()
b.checkName()
console.log('a.checkName === b.checkName',a.checkName === b.checkName)
a.checkName = function(){
console.log('new checkName')
}
a.checkName()
b.checkName()
</script>
</body>
</html>
1.5 使用prototype原型减小消耗
因为类里面的方法都是通过this定义的,所以每次通过 new 关键字创建新对象的时候,新对象都会对类的this上的属性进行赋值,所以这些新创建的对象都会有一套自己的方法。但是有时候这么做造成的消耗是很奢侈的(每一个新对象一个比作一个朋友,对象里的方法比作搭出租车,如果每个朋友做的车都不一样,那肯定要消耗更多的钱。但是如果朋友拼的是同一辆车,那消耗自然就降低了),我们需要处理一下。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
var checkObject = function(){}
checkObject.prototype.checkName = function(){
console.log('checkName')
}
checkObject.prototype.checkEmail = function(){}
checkObject.prototype.checkPassword = function(){}
var a = new checkObject()
var b = new checkObject()
console.log('a === b',a === b)
a.checkName()
b.checkName()
console.log('a.checkName === b.checkName',a.checkName === b.checkName)
console.log('a.checkName === checkObject.checkName',a.checkName === checkObject.checkName)
console.log('a.checkName === checkObject.prototype.checkName',a.checkName === checkObject.prototype.checkName)
a.checkName = function(){
console.log('new checkName')
}
a.checkName()
b.checkName()
console.log('a.checkName === checkObject.prototype.checkName',a.checkName === checkObject.prototype.checkName)
console.log('b.checkName === checkObject.prototype.checkName',b.checkName === checkObject.prototype.checkName)
</script>
</body>
</html>
可以看到 a 和 b 是不同的对象,但是他们的 .checkName() 方法是同一个,都是指向checkObject.prototype.checkName。
但是如果对 a.checkName方法重新定义的话,就不再指向checkObject.prototype.checkName。
1.6 prototype原型混用导致覆盖
上面使用prototype原型声明方法的方式要将prototype写很多遍,可以直接使用以下这种写法
var checkObject = function(){}
// 直接使用prototype对象
checkObject.prototype = {
checkName:function(){
console.log('checkName')
},
checkEmail:function(){},
checkPassword:function(){},
}
但是两种方法不能够混用,否则如果将prototype对象赋值新对象的时候就会将原来的通过 prototype. 方式复制的方法覆盖。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
var checkObject = function(){}
// 往prototype对象上一个一个加方法
checkObject.prototype.checkName = function(){
console.log('checkName')
}
checkObject.prototype.checkEmail = function(){}
checkObject.prototype.checkPassword = function(){}
// 直接使用prototype对象
checkObject.prototype = {
checkId: function(){
console.log('checkId')
}
}
var a = new checkObject()
a.checkId()
a.checkName()
</script>
</body>
</html>
1.7 方法的链式调用(优雅)
上面使用的prototype原型可以在类上一次性定义多个原型方法,并且被通过new创建的对象继承,但是在使用的时候仍然还需要多次书写对象本身(比如1.5中的a)。但是这是可以避免的,只要在声明的每一个方法的末尾处将当前的对象返回,在JavaScript中this指向的就是当前对象,所以我们可以将它返回。改动如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
// 对象收编方法
var checkObject1 = {
checkName: function(){
console.log('checkName1')
// 返回当前对象checkObject1
return this
},
checkEmail:function(){
console.log('checkEmail1')
return this
},
checkPassword:function(){
console.log('checkPassword1')
return this
}
}
// 方法的链式调用
checkObject1.checkName().checkEmail().checkPassword()
// 类的原型对象收编方法
var checkObject2 = function(){}
checkObject2.prototype = {
checkName: function(){
console.log('checkName2')
// 返回原型对象 checkObject2.prototype
return this
},
checkEmail:function(){
console.log('checkEmai2')
return this
},
checkPassword:function(){
console.log('checkPassword2')
return this
}
}
var a = new checkObject2()
// 类的原型对象也可以使用链式调用
a.checkName().checkEmail().checkPassword()
</script>
</body>
</html>
1.8 定义一个可以为函数添加多个方法的addMethod()方法
前面提到可以在函数对象中通过原型对象prototype收编多个变量函数,那么Function作为函数的祖先是否也可以呢?
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
// 可以在所有函数的祖先类Function的原型对象上定义方法
Function.prototype.checkName = function(){
console.log('checkName')
}
var a = function(){}
a.checkName()
var b = new Function()
b.checkName()
// 上面的方式虽然可以收编很多变量函数,但是这么做会污染原生对象Function,造成不必要的开销。
// 但是我们可以只在原生对象Function上添加一个添加方法的功能方法
Function.prototype.addMethod = function(name,fn){
this[name] = fn
}
var method = function(){}
method.addMethod('checkEmail', function(){
console.log('checkEmail')
})
method.checkEmail()
</script>
</body>
</html>
从上面的代码和控制台结果可以知道,Function也可以通过prototype收编变量函数,但是如果按照这种方法频繁的添加,会导致每个函数都有这些方法,从而造成不必要的开销。所以我们可以在原生对象Function上添加一个添加方法的功能方法。
1.9 定义一个既可以为函数原型添加多个方法也可以为自身添加多个方法的addMethod()方法
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
// 给Function的原型添加方法的方法
Function.prototype.addMethod = function(name,fn){
this.prototype[name] = fn
return this
}
// 直接给Funtion使用 addMethod 添加方法
Function.addMethod('fun1',function(){
console.log('fun1!')
return this
})
Function.addMethod('fun2',function(){
console.log('fun2!')
return this
})
//
// var method = function(){}
var method = new Function()
method.fun1().fun2()
method.addMethod('fun3',function(){
console.log('fun3!')
return this
})
var m = new method()
m.fun3()
</script>
</body>
</html>
番外篇:prototype和__proto__是什么以及两者的区别
相信很多初学JavaScript的同学和我一样,可能听过prototype和__proto__两个属性,但是在实际的项目开发中很少或者几乎没有用到。那么这两个属性究竟是什么?学习这两个属性又有什么用处呢?这部分的内容放到下一篇中prototype和__proto__的来历和使用讲解。
下一章 面向对象编程之封装