一.作用域
在JavaScript总结(2)里也提到过。
作用域是可访问变量的集合。
在 JavaScript 中, 对象和函数同样也是变量。
在 JavaScript 中, 作用域为可访问变量,对象,函数的集合。
JavaScript 函数作用域: 作用域在函数内修改。
1.JavaScript 全局变量
变量在函数外定义,即为全局变量。
全局变量有 全局作用域: 网页中所有脚本和函数均可使用。
因为局部变量只作用于函数内,所以不同的函数可以使用相同名称的变量。
局部变量在函数开始执行时创建,函数执行完后局部变量会自动销毁。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>作用域</title>
<script type="text/javascript">
//全局变量
var src="hello";
//声明一个函数
function test(){
alert("src=="+src);//src==hello
}
test();
</script>
</head>
<body>
</body>
</html>
2.JavaScript 局部变量
变量在函数内声明,变量为局部作用域。
局部变量:只能在函数内部访问。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>作用域</title>
<script type="text/javascript">
//声明函数
function test(){
//局部变量
var src="我今天心情不好!";
alert("你怎么了?---"+src);
}
test();
</script>
</head>
<body>
</body>
</html>
3.函数内没有声明
如果变量在函数内没有声明(没有使用 var 关键字),该变量为全局变量。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>作用域</title>
<script type="text/javascript">
//声明一个函数
function test1(){
//局部变量
var name="xiaoshao";
age=20;//没有声明变量;window对象的变量则为全局变量。
alert("我叫"+name+",今年"+age+"岁了。");
}
//声明一个函数
function test2(){
//局部变量
var name="xiaoli";
alert("我叫"+name+",今年"+age+"岁了。");
}
test1();
test2();
</script>
</head>
<body>
</body>
</html>
4.函数参数
函数参数只在函数内起作用,是局部变量。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>作用域</title>
<script type="text/javascript">
//声明一个函数,函数名称中的参数为局部变量
function test(src){
alert(src);
}
test("今天真是糟糕透了!");//参数
</script>
</head>
<body>
</body>
</html>
5.覆盖变量
全局变量,或者函数,可以覆盖 window 对象的变量或者函数。
例子:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>作用域</title>
<script type="text/javascript">
window.src="我心情很糟糕!";//window对象中的变量
var src="我脾气特别暴躁!";//全局中的变量
function test(){
alert(src);//全局变量覆盖window中变量
}
test();
</script>
</head>
<body>
</body>
</html>
局部变量,可以覆盖 window 对象,全局变量和函数。
例子:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>作用域</title>
<script type="text/javascript">
var src="我离开一会";//全局变量
function test(){
var src="我消失一段时间";//局部变量
alert(src);//局部变量覆盖全局变量
}
test();
</script>
</head>
<body>
</body>
</html>
二.闭包
1.闭包是什么?
一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure)。闭包是通过在对一个函数调用的执行环境中返回一个函数对象构成的。
闭包就是能够读取其他函数内部变量的函数。
闭包让你可以在一个内层函数中访问到其外层函数的作用域。在 JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。只有函数内部的子函数才能读取局部变量,所以说,闭包可以简单理解成"定义在一个函数内部的函数"。
所以,在本质上,闭包是将函数内部和函数外部连接起来的桥梁。
2.闭包的格式
格式:
<script>
var local="变量";
function foo(){
console.log(local);
}
</script>
有一个局部变量 local,有一个函数 foo,foo 里面可以访问到 local 变量。这就是一个闭包。
「函数」和「函数内部能访问到的变量」的总和,就是一个闭包。
3.闭包的作用
闭包可以用在许多地方。它的最大用处有两个,一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中,不会在调用后被自动清除。
闭包很有用,因为它允许将函数与其所操作的某些数据(环境)关联起来。这显然类似于面向对象编程。在面向对象编程中,对象允许我们将某些数据(对象的属性)与一个或者多个方法相关联。
因此,通常你使用只有一个方法的对象的地方,都可以使用闭包。
在 Web 中,你想要这样做的情况特别常见。大部分我们所写的 JavaScript 代码都是基于事件的 — 定义某种行为,然后将其添加到用户触发的事件之上(比如点击或者按键)。我们的代码通常作为回调:为响应事件而执行的函数。
例如:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>闭包</title>
<script type="text/javascript">
function ming() {
var lives = 50;
window.奖励一条命 = function() {
lives += 1;
return lives;
}
window.死一条命 = function() {
lives -= 1;
return lives;
}
return window.奖励一条命;
return window.死一条命;
}
function shao() {
var a = ming();
var b = window.奖励一条命();
var c = window.死一条命();
alert("恭喜你过关了,奖励一条命,现在命数为"+b);
alert("game over,减去一条命,现在命数为"+c);
}
shao();
</script>
</head>
<body>
</body>
</html>
4.使用闭包的注意点
(1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
(2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。
5.关于闭包的谣言
闭包会造成内存泄露?
Internet Explorer Web 浏览器(在 IE 4 到 IE 6 中核实)的垃圾收集系统中存在一个问题,即如果 ECMAScript 和某些宿主对象构成了 "循环引用",那么这些对象将不会被当作垃圾收集。此时所谓的宿主对象指的是任何 DOM 节点(包括 document 对象及其后代元素)和 ActiveX 对象。如果在一个循环引用中包含了一或多个这样的对象,那么这些对象直到浏览器关闭都不会被释放,而它们所占用的内存同样在浏览器关闭之前都不会交回系统重用。
当两个或多个对象以首尾相连的方式相互引用时,就构成了循环引用。比如对象 1 的一个属性引用了对象 2 ,对象 2 的一个属性引用了对象 3,而对象 3 的一个属性又引用了对象 1。对于纯粹的 ECMAScript 对象而言,只要没有其他对象引用对象 1、2、3,也就是说它们只是相互之间的引用,那么仍然会被垃圾收集系统识别并处理。但是,在 Internet Explorer 中,如果循环引用中的任何对象是 DOM 节点或者 ActiveX 对象,垃圾收集系统则不会发现它们之间的循环关系与系统中的其他对象是隔离的并释放它们。最终它们将被保留在内存中,直到浏览器关闭。
闭包非常容易构成循环引用。如果一个构成闭包的函数对象被指定给,比如一个 DOM 节点的事件处理器,而对该节点的引用又被指定给函数对象作用域中的一个活动(或可变)对象,那么就存在一个循环引用。DOM_Node.onevent ->function_object.[[scope]] ->scope_chain ->Activation_object.nodeRef ->DOM_Node。形成这样一个循环引用是轻而易举的,而且稍微浏览一下包含类似循环引用代码的网站(通常会出现在网站的每个页面中),就会消耗大量(甚至全部)系统内存。
多加注意可以避免形成循环引用,而在无法避免时,也可以使用补偿的方法,比如使用 IE 的 onunload 事件来来清空(null)事件处理函数的引用。时刻意识到这个问题并理解闭包的工作机制是在 IE 中避免此类问题的关键。
参见司徒正美的这篇文章【https://www.cnblogs.com/rubylouvre/p/3345294.html】。
三.JavaScript原型
1.原型
定义:原型是function对象的一个属性,它定义了构造函数制造出的对象的公共祖先。通过该构造函数产生的对象,可以继承原型的属性和方法。原型也是对象。
JavaScript是基于原型的我们创建的每个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。
图解:
- 每一个构造函数都拥有一个 prototype 属性,这个属性指向一个对象,也就是原型对象
- 原型对象默认拥有一个constructor属性,指向指向它的那个构造函数
- 每个对象都拥有一个隐藏的属性__proto__,指向它的原型对象
咋样让函数只定义一次,而求出多组数组的和?
使用prototype原型对象来做
例子:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>原型</title>
<script type="text/javascript">
//数组
var arr1=[0,1,2,3,4,5,6,7,8,9];
var arr2=[1,8,3,6,5,4,7,2,9];
//原型
Array.prototype.getSum = function() {
//求和
var sum = 0;
for(var i = 0; i < this.length; i++) {
sum += this[i];
}
return sum;
}
//得和值
console.log(arr1.getSum());//45
console.log(arr2.getSum());//45
</script>
</head>
<body>
</body>
</html>
不管有多少组数组都可以使用这个方法,用prototype定义一次函数来做。
2.原型链
原型对象也可能拥有原型,并从中继承方法和属性,一层一层、以此类推。这种关系常被称为原型链 (prototype chain),它解释了为何一个对象会拥有定义在其他对象中的属性和方法
在对象实例和它的构造器之间建立一个链接(它是__proto__属性,是从构造函数的prototype属性派生的),之后通过上溯原型链,在构造器中找到这些属性和方法
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>原型链</title>
<script type="text/javascript">
//函数
function Person(name, age) {
this.name = name;
this.age = age;
}
//Person的定义方法
Person.prototype.shao = function() {
//Person原型调用的内容
return "你调用的原型上面的方法";
}
//定义另一个构造函数
function Aminal() {}
//在Aminal的原型上面定义方法speek方法
Aminal.prototype.shao = function() {
//Aminal原型调用的内容
return "我是Aminal的showName";
}
var p1 = new Person("xs", 20);
var p2 = new Person("sz", 23);
//强行改变p2的原型链
p2.__proto__ = Aminal.prototype; //将p2的__proto__指向Aminal的原型
//var p3 = new Person("zz", 23);
//p3.__proto__ = null;//p3的__proto__的null
//alert(p3.shao()); //报错
alert(p1.shao()); //你调用的原型上面的方法
alert(p2.shao()); //我是Aminal的showName
</script>
</head>
<body>
</body>
</html>
p1调用的原型上面的方法
因p2被强行改变了原型链
3.特点
JavaScript对象是通过引用来传递的,我们创建的每个新对象实体中并没有一份属于自己的原型副本。当我们修改原型时,与之相关的对象也会继承这一改变。
4.总结
每个对象的__proto__都是指向它的构造函数的原型对象prototype的
person1.__proto__ === Person.prototype
构造函数是一个函数对象,是通过Function构造器产生的
Person.__proto__ === Function.prototype
原型对象本身是一个普通对象,而普通对象的构造函数都是Object
Person.prototype.__proto__ === Object.prototype
刚刚上面说了,所有的构造器都是函数对象,函数对象都是 Function构造产生的
Object.__proto__ === Function.prototype
Object的原型对象也有__proto__属性指向null,null是原型链的顶端
Object.prototype.__proto__ === null