JavaScript语言最重要的几个部分:
* 引用(reference)
* 作用域(scope)
* 闭包(closure)
* 上下文(context)
1 引用
“引用”是一个指向对象实际位置的指针。
但是有一个前提:实际的对象肯定不会是引用。字符串永远是字符串,数组永远是数组。不过多个变量却能指向同一对象。
此外,对象可以包含一系列属性(property),这些属性也都不过是其他对象(字符串、数字、数组等待)的引用。如果多个变量指向同一个对象,那该对象的类型一改变,所有这些变量也会跟着响应改变。
多个变量引用同一个对象的例子
// 创建obj空对象
var obj = {};
// objRef 是另一个对象的引用
var objRef = obj;
// 修改原对象的一个属性
obj.oneProperty = true;
// 这个修改在两个变量中都反应出来了,因为他们引用的是同一个对象
alert(obj.oneProperty === objRef.oneProperty) // true
自修改对象在JavaScript中很少见。
自修改对象的例子
var items = ['one', 'two', 'three']
// 创建一个数组的引用
var itemsRef = items;
// 给原数组添加一个元素
items.push('four');
alert(items === itemsRef) // true
必须要记住的是,引用指向的只能是具体的对象,而不是另一个引用。JavaScript里的结果是沿着引用链一直上溯到原来那个对象。
修改对象的引用,同时保持完整性的例子
var items = ['one', 'two', 'three']
// 将 itemsRef 置为 items 的引用
var itemsRef = items;
// 将items置为一个新的对象
items = ['new', 'array']
alert(items != itemsRef) // true
实际对象已经改变了,但原来指向它的引用仍然指向旧的对象。
修改对象而生成新对象例子
var item = 'test';
// itemsRef 指向同一个字符串对象
var itemRef = item;
// 将新文本接在这个字符串后面
// 注意:这会创建一个新对象,而非修改原对象
item += 'ing';
// item 和 itemsRef 的值不相等了,因为系的字符串对象已被创建
alert(item != itemRef) // true
2 函数重载和类型检查
函数重载必须依赖两件事情:
1. 判断传入参数数量的能力
2. 判断传入参数类型的能力
参数数量
JavaScript的每个函数都带有一个仅在这个函数范围内作用的变量称为参数(argument),它是一个包含所有传给函数的参数的伪数组(pseudo-array),所以它并非真正意义的数组(也就是说你不能修改它,也不能用push()来添加新元素),但可以访问其中的元素,它也具有.length属性。
JavaScript中函数重载的两个例子
function sendMessage(msg, obj){
if ( arguments.length == 2 ) {
// 给对象发送消息
obj.handleMsg(msg);
} else {
alert(msg);
}
}
sendMessage ('hello world');
// 传入对象,负责用另一套办法显式信息
sendMessage('how are you', {
handleMsg: function (msg) {
alert('This is a custom message: ' + msg)
}
})
// 一个接受任意数量参数并将其转化为数组的函数
function makeArray () {
var arr = [];
for (var i=0; i< arguments.length; i++) {
arr.push(arguments[i])
}
return arr;
}
console.log(makeArray(1,2,3,4,5)) // [1, 2, 3, 4, 5]
如果没有提供参数,它的类型必为undefined.
显示错误信息和默认信息的例子
function displayerror (msg) {
if ( typeof msg == 'undefined') {
msg = 'An error occurred.';
}
alert(msg)
}
displayerror ()
类型检查
typeof 是类型检查。既然JavaScript是动态类型的语言,有许多可以检查变量的类型。
1. 第一种使用typeof操作符
此方法只提供了一个字符串名称,用于表达变量的类型。
当变量不是object或array类型时,都能检测。但是无法区分Object和Array,它们都是返回object。
var obj={}
var arr = [1,2,3,4]
console.log(typeof obj) // object
console.log(typeof arr) // object
- 第二种检查对象类型的方法,需要引用所有JavaScript对象都带有的一个属性,称为构造函数。这一属性引用的是原本用来构造该对象的那个函数
使用构造函数属性来判断对象的类型的例子
var obj = {an: 'object'}
var arr = ['an', 'array']
var fn = function(){}
var str = 'a string'
var num = 55
var boo = true
function User() {}
var newFn = new User()
console.log(typeof(obj) + " : " + obj.constructor)
// object : function Object() { [native code] }
console.log(obj.constructor == Object) // true
console.log(typeof(arr) + " : " + arr.constructor)
// object : function Array() { [native code] }
console.log(arr.constructor == Array) // true
console.log(typeof(fn) + " : " + fn.constructor)
// function : function Function() { [native code] }
console.log(fn.constructor == Function) // true
console.log(typeof(num) + " : " + num.constructor)
// number : function Number() { [native code] }
console.log(num.constructor == Number) // true
console.log(typeof(boo) + " : " + boo.constructor)
// boolean : function Boolean() { [native code] }
console.log(boo.constructor == Boolean) // true
console.log(typeof(newFn) + " : " + newFn.constructor)
// object : function User() {
// this.user = 'people'
// }
console.log(newFn.constructor == User) // true
3 作用域
在JavaScript里,作用域是由函数划分的,而不是由块划分的(如while,if 和for语句中间)。导致的结果是某些代码不好理解
var foo = 'test'
if (true) {
var foo = 'new test'
}
function test (){
var foo = 'old test'
console.log(foo == 'old test') // true
}
test()
console.log(foo == 'new test') // true
可见函数是有作用域的
JavaScript中全局作用域和window对象的例子
// 一个全局作用域下的变量,存储了字符串‘test'
var foo = 'test'
// 全局变量和window对象的foo属性是一致的
console.log(window.foo == foo) // true
如果变量没有显式定义,它就是全局定义的。
function test (){
foo = 'new test'
}
test()
console.log(foo == 'new test') // true
console.log(window.foo == 'new test') // true
4 闭包
闭包(closure)意味着内层的函数可以引用存在于包围它的函数内的变量,即使外层函数的执行已经终止。
闭包的例子
var obj = document.getElementById('main')
obj.style.border = '1px solid red'
// 初始化一个在1秒后执行的函数
setTimeout(function(){
// 引用了全局变量obj
obj.style.display = 'none'
},1000)
function delayedAlert(msg, time){
setTimeout(function(){
// 它将使用包含本函数的外围函数传入的msg变量
alert(msg)
},time)
}
delayedAlert('welcome' , 2000)
闭包的额外作用。在函数式程序语言里,有一种称为Curry化的技术。
Curry化是一种通过把多个参数填充到函数体中,实现将函数转化为一个新的经过简化的函数的技术。
用闭包实现的函数Curry化的例子
// 数字求和函数的函数生成器
function addGenerator (num) {
// 返回一个简单的函数,求两个数字的和,其中第一个数字来自生成器
return function(toAdd) {
return num + toAdd
}
}
// addFive 现在包含一个接受单一参数的函数,这个函数能求得5加上该函数的和
var addFive = addGenerator(5)
// 传入参数求和
console.log(addFive(4)) // 9
闭包的另一种使用方式:自执行的匿名函数。
自执行匿名函数可以把所有属于全局的变量都隐藏起来
使用匿名函数来隐藏全局作用域变量的例子
(function(){
var msg = 'Thanks for visiting'
window.onload = function(){
alert(msg)
}
})()
在for循环中使用闭包,利用闭包来引用循环的计数器
var obj = document.getElementById('main')
var items = ['click', 'keypress']
// 使用一个自执行的匿名函数来激发出作用域
for (var i = 0; i< items.length; i++) {
(function(){
// 记住在这个作用域内的值
var item = items[i]
将一个函数绑定到该元素
obj['on'+item] = function() {
// item 引用本for循环上下文所属作用域中的一个父变量
alert('Thanks for your ' + item)
}
})()
}
5 上下文对象
上下文对象是通过this变量体现的,这个变量永远指向当前代码所处的对象中。即使在全局上下文中,this变量指向window对象。
在上下文对象内使用函数并将其上下文切换为另一个变量的例子
var obj = {
yes: function () {
// this == obj
this.val = true
},
no: function () {
this.val = false
}
}
// obj对象没有val属性
console.log(obj.val == null) // true
// 指向yes函数后,将val属性与obj对象关联起来
obj.yes()
console.log(obj.val == true) // true
// 把window.no 指向obj.no并执行
var no = obj.no
no()
// obj对象的val不变
console.log(obj.val == true) // true
// window的val属性被更新了
console.log(val == false) // true
把obj.no变量的上下文切换为window变量时,代码变得不好理解
JavaScript提供了一套方法来让这一过程变得更好理解和实现
修改函数上下文对象的例子
<div id="main" style="width:100px; height:100px"></div>
<script>
function changeBackgroud (color) {
this.style.background = color
}
// 在window对象中调用次函数会失败,因为window对象没有style属性
// changeBackgroud('red')
var main = document.getElementById('main')
// changeBackgroud函数虽然在全局作用域下执行,但是把this指向了main
// 使用call方法将它的颜色置为黑色。call方法将上下文对象设置为第一个参数,并将其他参数作为原函数的参数
changeBackgroud.call(main,'black');
// 设置body元素颜色的函数
function setBodyColor () {
// apply 方法将上下文对象设置为第一个参数制定的body元素,第二个参数是传给函数的所有参数的数组
changeBackgroud.apply(document.body, arguments)
}
setBodyColor('red')
</script>