1. js介绍
js是为了实现网络交互而是设计的脚本语言,有以下三部分组成
- ECMAScript,由ECMA-262定义,提供核心功能,其实web浏览器是其宿主环境之一,组成部分:语法,类型,语句,关键字,保留字,操作符,对象
- 文档对象模型(DOM),提供访问和操作页面的接口和方法,DOM把整个页面映射为一个多层节点结构,根据其提供的API,开发人员可自如增删改换节点。
DOM1=DOM Core(映射文档结构) + DOM html(对html的对象和方法)
DOM2新模块:
- DOM视图(DOM views),跟踪不同文档的视图接口
2 .DOM事件(DOM Events),定义事件和时间处理的接口 - DOM样式(DOM style),基于CSS为元素应用样式接口
- DOM遍历和范围(DOM Traversal and Range),遍历和操作文档树的接口
DOM3新模块:
.DOM验证(DOM Validation)文档验证方法并支持XML1.0规范
- 浏览器对象模型(BOM)
根本来说,处理浏览器的窗口和框架,但人们习惯将一下扩展也算成BOM的一部分
1.弹出新浏览器窗口
2.移动 关闭 缩放浏览器
- 提供浏览器详细信息的navigator对象
4.浏览器所加载页面的详细信息location对象
5.用户显示器分辨率信息screen对象
6.对cookie的支持
7.自定义对象
2.在html中使用js
通过<script>引入,常用属性
1.async,表示应该立即下载脚本,但不妨碍页面的其他操作,会在页面load之前进行
2.defer,表示文档可以延迟到文档全部被解析和显示后再执行
3.src,执行代码的外部文件
4.type,表示编写代码使用的脚本语言的内容类型(也成为MIME类型),一般是text/javascript(这个是默认的)
使用方式
1.直接放入内部元素,内部/script应替换成</script>,以免造成误解
2.嵌入式
一般放在body后面,这样在解析JS语言之前,页面内容已经完全展示在浏览器上,不要出现浏览器明显延迟的问题
<noscript>在不支持js的浏览器显示替代的内容
3.基本概念
- 标识符
驼峰大小写格式,如firstStudent - 注释
//单行
/*
*多行
*/ - 严格模式
"use strict" - 变量
松散类型
var message; //没初始化,因此是undefined
不使用var标识符的话就是全局变量
- 类型typeof
undefined,boolean, string,object,number,function - number:
Infinity(正无穷)
-Infinity(负无穷)
isFinite()判断是否有穷
NaN非数值isNaN()判断是否为数值
将非数值转化为数值
Number parseInt parseFloat - object对象
初始化 var o = new object();
属性方法
Constructor创建当前对象的函数
hasOwnProperty(propertyName)给定的属性在当前实例中是否出现
** isPrototypeOf(object) **别传入的对象是否为另一个对象的原型
PropertyIsEnumerable判断给定的属性是否可以用 for...in 语句进行枚举。
toLocaleString 方法返回执行环境对应的字符串。
toString()对象字符串表示
valueOf()返回对象的字符串数值或布尔表示 - 语句
if do-while while for switch
for-in枚举
lable-代码中添加标签,以便后来使用???
break 跳出整个循环 continue跳出当前循环
with 作用域设定到一个对象中
4.变量,作用域,内存问题
执行环境类型——全局,局部
其中内部环境可通过作用域链访问所有的外部环境,但外部环境不能内部环境的任何变量和函数
4.1延长作用域链
执行下列语句时,作用域延长
try-catch
with
- 栗子1
function buildUrl(){
var qs="?debug=true";
with(location){
var url=href+qs;
}
return url;
}
var result=buildUrl();
alert(result);
输出: 静态页地址+qs的值。
原因:
1.由于with语句块中作用域的‘变量对象’是只读的,所以在他本层定义的标识符,不能存储到本层,而是存储到它的上一层作用域
2.with代码块中,javascript引擎对变量的处理方式是:先查找是不是该对象的属性,如果是,则停止。如果不是继续查找是不是局部变量。
var o={href:"sssss"};
var href="1111";
function buildUrl(){
var qs="?debug=true";
with(o){
href="2222";
var url=href+qs;
}
return url;
}
var result=buildUrl();
alert(result);
alert(href);
输出: 结果:2222?debug=true + 1111
4.2 JavaScript作用域之没有块级作用域
<script>
function test(o){
var i=0;
if(typeof o == "object"){
var j=0;
for(var k=0; k<10; k++){
document.writeln("k="+k);
}
document.writeln("k2="+k);
}
document.writeln("j="+j);
}
test(new String("pppp"));
</script>
输出结果为:k=0 k=1 k=2 k=3 k=4 k=5 k=6 k=7 k=8 k=9 +++k=10 j=0
这是因为,由于JavaScript中不存在块级作用域,因此函数中声明的所有变量,无论是在哪里声明的,在整个函数中它们都有定义。
<script>
var scope="global";
function f(){
alert(scope);
var scope="local";
alert(scope);
}
f();
</script>
结果:第一个alert输出:underfined而不是global,第二个alert输出local
与下列函数等价
function f(){
var scope;
alert(scope);
var scope="local";
alert(scope);
}
5.引用类型
5.1Object类型
创建有两种方式:
1.new操作符后跟Object构造函数
var person = new Object();
person.name = "Nicholas";
person.age = 29;
2.字面量表示法
var person = {
name: "Nicholas",
age: 20,
5:true
};
这里所有的数值属性名都会被转换为字符串
访问方法
alert(person.name);
alert(person[name])
5.2Arrary类型
构造
var colors = ["red","green"];
var arrays = new Array(3);//new可省略
var names = new Array["Mia"];
数组尾部添加新项
colors[colors.length] = "pink"
js判断数组类型的方法
- instanceof
var a=[];
console.log(a instanceof Array) //返回true
- constructor
constructor 属性返回对创建此对象的数组函数的引用
console.log([].constructor == Array);
console.log({}.constructor == Object);
console.log("string".constructor == String);
console.log((123).constructor == Number);
console.log(true.constructor == Boolean);
较为严谨并且通用的方法:
function isArray(object){
return object && typeof object==='object' &&
Array == object.constructor;
}
!!注意:
使用instaceof和construcor,被判断的array必须是在当前页面声明的!比如,一个页面(父页面)有一个框架,框架中引用了一个页面(子页面),在子页面中声明了一个array,并将其赋值给父页面的一个变量,这时判断该变量,Array == object.constructor;会返回false;
原因:
1、array属于引用型数据,在传递过程中,仅仅是引用地址的传递。
2、每个页面的Array原生对象所引用的地址是不一样的,在子页面声明的array,所对应的构造函数,是子页面的Array对象;父页面来进行判断,使用的Array并不等于子页面的Array;切记,不然很难跟踪问题!
- 特性判断法
function isArray(object){
return object && typeof object==='object' &&
typeof object.length==='number' &&
typeof object.splice==='function' &&
//判断length属性是否是可枚举的 对于数组 将得到false
!(object.propertyIsEnumerable('length'));
}
有length和splice并不一定是数组,因为可以为对象添加属性,而不能枚举length属性,才是最重要的判断因子。备注:如果 proName 存在于 object 中且可以使用一个 For…In 循环穷举出来,那么 propertyIsEnumerable 属性返回 true
- 最简单的方法
function isArray(o) {
return Object.prototype.toString.call(o) === ‘[object Array]‘;
}
转化方法
- arrayObject.toLocaleString()
返回值:
arrayObject 的本地字符串表示。 - toString()
把数组转换为字符串,并返回结果。返回值与没有参数的 join() 方法返回的字符串相同。
alert(colors.join(",")) - valueOf
valueOf()方法返回 Array 对象的原始值。实际上,为了创建这个字符串会调用数组的每一项的toString()方法。
栈方法
栈是一种LIFO(Last-In-First-Out,后进先出)的数据结构ECMAScript为数组提供了push()和pop()方法,可以实现类似栈的行为。分别添加到数组末尾和从数组末尾移除最后一项。
队列方法
shift:从数组中把第一个元素删除,并返回这个元素的值。
unshift: 在数组的开头添加一个或更多元素,并返回新的长度
push:在数组的中末尾添加元素,并返回新的长度
pop:从数组中把最后一个元素删除,并返回这个元素的值。
unshift比push要慢差不多100倍!因此,平时还是要慎用unshift,特别是对大数组。那如果一定要达到unshift的效果,可以借助于Array的reverse方法,Array的reverse的方法能够把一个数组反转。先把要放进数组的元素用push添加,再执行一次reverse
重排序方法
reverse()——反转
sort()——升序排列数组
操作方法
-
concat()方法
基于当前数组中所有项创建新数组。
var colors = ["red","green","blue"];
var colors2 = colors.concat("yellow",["black","brown"]);
alert(colors);
alert(colors2);
结果为colors为数组[“red”,”green”,”blue”];
colors2为数组[“red”,”green”,”blue”,”yellow”,”black”,”brown”];
concat()方法只是用当前数组重新创建一个新的数组,因此当前数组保持不变(colors数组不变
-
slice()方法
slice()方法:基于当前数组中的一个或多个项创建一个新数组。
slice()方法中可以有一个或者两个参数(代表数组的索引值,0,1,2……)。
接收一个参数时:返回当前数组中从此参数位置开始到当前数组末尾间所有项。
接收两个参数时:返回当前数组中两个参数位置间的所有项,但不返回第二个参数位置的项。
参数也可以为负数,表示从末尾算起,-1代表最后一个,使用方法和正数一样。
var colors = ["red","green","blue","yellow","black","brown"];
var colors2 = colors.slice(2);
var colors3 = colors.slice(1,4);
var colors4 = colors.slice(2,-2);
var colors5 = colors.slice(-3,-1);
console.log(colors2);
console.log(colors3);
console.log(colors4);
console.log(colors5);
结果为:
[“blue”, “yellow”, “black”, “brown”]
[“green”, “blue”, “yellow”]
[“blue”, “yellow”]
[“yellow”, “black”]
-
splice()
splice()主要用途是向当前数组的中间插入项,可以进行删除、插入、替换操作。会返回一个数组,包含从原始项中删除的项(若果没有删除,返回一个空数组)
删除:两个参数,删除起始项的位置和删除的项数。
var colors = ["red","green","blue"];
var removed = colors.splice(1,2);
alert(colors); //red
alert(removed); //green,blue
插入:在指定位置插入任意数量项,包括两个基本参数(即删除操作中的两个参数类型)和要插入项的参数,两个基本参数为起始位置和0(要删除的项数应为0项),要插入的项参数可以是任意个(”red”,”green”,”blue”)。
var colors = ["red","green","blue"];
var removed = colors.splice(1,0,"yellow","orange");
alert(colors); //"red","yellow","orange","green","blue"
alert(removed); //空数组
替换:向指定位置插入任意数量的项同时删除任意数量的项,插入项数和删除项数可以不同。参数包括两个基本参数(即删除操作中的两个参数类型)和要插入项的参数。
var colors = ["red","green","blue"];
var removed = colors.splice(1,1,"purple","black");
alert(colors); //"red","purple","black","blue"
alert(removed); //"green"
位置方法
indexOf() 方法
返回某个指定的字符串值在字符串中首次出现的位置。lastIndexOf() 方法
返回一个指定的字符串值最后出现的位置,在一个字符串中的指定位置从后向前搜索。substr
substr(start,length)表示从start位置开始,截取length长度的字符串。
var src="images/off_1.png";
alert(src.substr(7,3));
弹出值为:offsubstring
substring(start,end)表示从start到end之间的字符串,包括start位置的字符但是不包括end位置的字符。
var src="images/off_1.png";
alert(src.substring(7,10));
弹出值为:off
迭代方法
var arr = [3,4,5,6,7,"a"];
var isNum = function(elem,index,AAA){
return !isNaN(elem);
}
var toUpperCase = function(elem){
return String.prototype.toUpperCase.apply(elem);
}
var print = function(elem,index){
console.log(index+"."+elem);
}
-
every
对数组中的每一项执行测试函数,直到获得对指定的函数返回 false 的项。使用此方法 可确定数组中的所有项是否满足某一条件,类似于&&的含义
var res = arr.every(isNum);
console.log(res);//false;
-
some
对数组中的每一项执行测试函数,直到获得返回 true 的项。 使用此方法确定数组中的所有项是否满足条件.类似于||的含义
res = arr.some(isNum);
console.log(res);//true
-
filter
对数组中的每一项执行测试函数,并构造一个新数组,返回 true的项被添加进新数组。 如果某项返回 false,则新数组中将不包含此项
res = arr.filter(isNum);
console.log(res);//[3, 4, 5, 6, 7]
-
map
对数组中的每一项执行函数并构造一个新数组,并将原始数组中的每一项的函数结添加进新数组。
res = arr.map(toUpperCase);
console.log(res);//["3", "4", "5", "6", "7", "A"]
-
forEach
对数组中的每一项执行函数,不返回值
res = arr.forEach(print);
console.log(res);
缩小方法
- reduce
array.reduce(callbackfn,[initialValue])
function callbackfn(preValue,curValue,index,array){}
**preValue
**: 上一次调用回调返回的值,或者是提供的初始值(initialValue)
**curValue
**: 数组中当前被处理的数组项
**index
**: 当前数组项在数组中的索引值
**array
**: 调用 reduce()
Date类型
Date.parse接受一个字符串参数,如果可以转化,将转换为对应的毫秒数,否则返回 NaN;
Date.UTC最少接受两个参数,分别表示年份和月份(0·11),其他的日期,小时(0-24)、分钟、秒,可以指定也可以不指定,不指定时默认为 0;
dateObject.getTime() 0~... 从GTM1970年1月1日0:00:00开始计算的毫秒数。
dateObject.getYear() 70~... 指定的年份减去1900,2000年后为4位数表示的年份。
dateObject.getFullYear() 1970~... 4位数年份,适用于版本4以上的浏览器。
dateObject.getMonth() 0~11 一年中的月份(1月为0)。
dateObject.getDate() 1~31 一月中的日期。
dateObject.getDay() 0~6 星期(星期日为0)。
dateObject.getHours() 0~23 一天中指定的小时数,采用24小时制。
dateObject.getMinutes() 0~59 指定小时内的分钟数。
dateObject.getSeconds() 0~59 指定分钟内的秒数。
dateObject.setTime(val) 0~... 从GTM1970年1月1日0:00:00开始计算的毫秒数。
dateObject.setYear(val) 70~... 指定的年份减去1900,2000年后为4位数表示的年份。
dateObject.setMonth(val) 0~11 一年中的月份(1月为0)。
dateObject.setDate(val) 1~31 一月中的日期。
dateObject.setDay(val) 0~6 星期(星期日为0)。
dateObject.setHours(val) 0~23 一天中的小时数,采用24小时值。
dateObject.setMinutes(val) 0~59 指定小时内的分钟数。
dateObject.setSecond(val) 0~59 指定小时内的秒数。
当前时间
var start = Date.now()
格式化
var d = new Date();
console.log(d); // 输出:Mon Nov 04 2013 21:50:33 GMT+0800 (中国标准时间)
console.log(d.toDateString()); // 日期字符串,输出:Mon Nov 04 2013
console.log(d.toGMTString()); // 格林威治时间,输出:Mon, 04 Nov 2013 14:03:05 GMT
console.log(d.toISOString()); // 国际标准组织(ISO)格式,输出:2013-11-04T14:03:05.420Z
console.log(d.toJSON()); // 输出:2013-11-04T14:03:05.420Z
console.log(d.toLocaleDateString()); // 转换为本地日期格式,视环境而定,输出:2013年11月4日
console.log(d.toLocaleString()); // 转换为本地日期和时间格式,视环境而定,输出:2013年11月4日 下午10:03:05
console.log(d.toLocaleTimeString()); // 转换为本地时间格式,视环境而定,输出:下午10:03:05
console.log(d.toString()); // 转换为字符串,输出:Mon Nov 04 2013 22:03:05 GMT+0800 (中国标准时间)
console.log(d.toTimeString()); // 转换为时间字符串,输出:22:03:05 GMT+0800 (中国标准时间)
console.log(d.toUTCString()); // 转换为世界时间,输出:Mon, 04 Nov 2013 14:03:05 GMT
RegExp
function
函数内部属性只要包括两个特殊的对象:arguments和this。
函数属性包括:length和prototype
函数方法(非继承)包括:apply()和call()
继承而来的函数方法:bind()、toString()、toLocaleString()、valueOf()
- 没有重载
js不存在重载的概念,后面的方法会覆盖先前的同名的方法。 - 函数声明和函数表达式
1)函数声明(Function Declaration);
// 函数声明
function funDeclaration(type){
return type==="Declaration";
}
2)函数表达式(Function Expression)。
// 函数表达式
var funExpression = function(type){
return type==="Expression";
}
上面的代码看起来很类似,感觉也没什么太大差别。但实际上,Javascript函数上的一个“陷阱”就体现在Javascript两种类型的函数定义上。下面看两段代码:
funDeclaration("Declaration");//=> true
function funDeclaration(type){
return type==="Declaration";
}
funExpression("Expression");//=>error
var funExpression = function(type){
return type==="Expression";
}
用函数声明创建的函数funDeclaration可以在funDeclaration定义之前就进行调用;而用函数表达式创建的funExpression函数不能在funExpression被赋值之前进行调用。
代码1段JS函数等同于:
function funDeclaration(type){
return type==="Declaration";
}
funDeclaration("Declaration");//=> true
代码2段JS函数等同于:
var funExpression;
funExpression("Expression");//==>error
funExpression = function(type){
return type==="Expression";
}
再来个例子
var sayHello;
console.log(typeof (sayHey));//=>function
console.log(typeof (sayHo));//=>undefined
if (true) {
function sayHey() {
console.log("sayHey");
}
sayHello = function sayHo() {
console.log("sayHello");
}
} else {
function sayHey() {
console.log("sayHey2");
}
sayHello = function sayHo() {
console.log("sayHello2");
}
}
sayHey();// => sayHey2
sayHello();// => sayHello
等同于
var sayHello;
function sayHey() {
console.log("sayHey");
}
function sayHey() {
console.log("sayHey2");
}
console.log(typeof (sayHey));//=>function
console.log(typeof (sayHo));//=>undefined
if (true) {
//hoisting...
sayHello = function sayHo() {
console.log("sayHello");
}
} else {
//hoisting...
sayHello = function sayHo() {
console.log("sayHello2");
}
}
sayHey();// => sayHey2
sayHello();// => sayHello
Javascript 中函数声明和函数表达式是存在区别的,函数声明在JS解析时进行函数提升,因此在同一个作用域内,不管函数声明在哪里定义,该函数都可以进行调用。而函数表达式的值是在JS运行时确定,并且在表达式赋值完成后,该函数才能调用。这个微小的区别,可能会导致JS代码出现意想不到的bug,让你陷入莫名的陷阱中。
- 函数的内部属性
-
arguments
有两个特殊的对象:arguments和this。其中,arguments它是一个类数组对象,包含着传入函数中的所有参数。虽然arguments的主要用途是保存函数参数,但这个对象含所有一个名叫callee的属性,该属性是一个指针,指向拥有这个arguments对象的函数。
下面这个非常经典的阶乘函数。
function factorial (num){
if(num <= 1){
return 1;
} else{
return num * factorial(num-1);
}
}
定义阶乘函数一般都会用到递归算法,如上面代码所示,在有函数名字,并且函数名字以后也不会改变的情况下,这种定义没问题。但是这个函数的执行与函数名factorial紧紧耦合在了一起,为了消除这种紧密耦合现象(函数名字改变等情况),可以使用arguments.callee。
function factorial(num){
if(num<=1){
return 1;
} else{
return num * arguments.callee(num-1);
}
}
在这个重写后的factorial()函数的函数体内,没有再引用函数名factorial。这样,无论引用函数时使用的是什么名字,都可以保证正常完成递归调用。例如:
var trueFactorial=factorial;
factorial=function()
{
return 0;
};
alert(trueFactorial(5));//120
alert(factorial(5));//0
在此,变量trueFactorial获得了factorial的值,实际上是另一个位置上保存了一个函数的指针。然后,我们又将一个简单的返回了0的函数赋值给了factorial变量。如果像原来factorial()那样不用使用arguments.callee,调用trueFactorial()就会返回0。可是,在解除了函数体内的代码与函数名的耦合状态之后,trueFactorial()仍然能够正常的计算阶乘;至于factorial(),它现在只是一个返回0的函数。
-
this
函数内部的另一个特殊对象是this,其行为与java和c#中的this大致类似。换句话说,this引用的是函数据以执行的环境对象,或则也可以说是this值(当在网页的全局作用中调用函数时,this对象引用的就是window)。如下例子:
window.color="red";
var o={color:"blue"};
function sayColor()
{
alert(this.color);
}
sayColor();//red
o.sayColor=sayColor;
o.sayColor();//blue
- apply()方法
接收两个参数:一个是在其中运行函数的作用域,另一个是参数数组。其中,第二个参数可以使Array的实例,也可以是arguments对象
unction sum(num1,num2)
{
return num1+num2;
}
function callSum1(num1,num2)
{
return sum.apply(this,arguments);//传入arguments对象
}
function callSum2(num1,num2)
{
return sum.apply(this,[num1,num2]);//传入数组
}
alert(callSum1(10,10));//20
alert(callSum2(10,10));//20
- call()方法
对于call()方法而言,第一个参数是this值没有变化,变化的是其余参数都直接传递给函数。换句话讲,在使用call()方法时,传递给函数的参数必须逐个列举出来,如下例子:
function sum(num1,num2)
{
return num1+num2;
}
function callSum(num1,num2)
{
return sum.call(this,num1,num2);
}
alert(callSum(10,10));//20
事实上,传递参数并非apply()和call()正真的用武之地;它们正真强大的地方是能够扩充函数赖以运行的作用域。如下例子:
window.color="red";
var o={color:"blue"};
function sayColor()
{
alert(this.color);
}
sayColor();//red
sayColor.call(this);//red
sayColor.call(window);//red
sayColor.call(o);//blue
这一次,sayColor()也是作为全局函数定义的,而且当在全局作用域中调用它时,它确实会显示“red”,因为对this.color的求值会转换成对window.color的求值。而sayColor.call(this)和sayColor.call(window),则是两种显示的在全局作用域中调用函数的方式,结果当然都会显示“red”。但是,当运行sayColor.call(o)时,函数的执行环境就不一样了,因为此时函数体内的this对象指向了o,于是结果显示的是“blue”。
使用call()或apply()来扩充作用域的最大好处,就是对象不需要与方法有任何耦合关系。在前面例子的第一个版本中,我们是先将sayColor()函数放到了对象o中,然后再通过o来调用它;而在这里重写的例子中,就不需要先前那个多余的步骤了。
- bind()
window.color="red";
var o={color:"blue"};
function sayColor()
{
alert(this.color);
}
var objectSayColor=sayColor.bind(o);
objectSayColor();//blue
在这里,sayColor()调用bind()并传入对象o,创建了objectSayColor()函数。objectSayColor()函数的this值等于o,因此即使是在全局作用域中调用这个函数,也会看到“blue”。
函数表达式
理解作用域链是理解闭包的关键;
(1)什么是执行环境
执行环境有二种: 全局执行环境;局部执行环境---function里面;
执行流进入函数执行时,创建执行环境;函数执行结束,回收!
(2)变量对象
理解变量对象非常重要,变量对象在函数中也称为 活动对象,我还是喜欢说成变量对象。
每一个执行环境都有一个变量对象,变量对象会保存这个执行环境的变量和方法;我们不难想到全局的变量对象是谁? window对象
我们在全局中添加变量和方法都是添加到window里。函数执行时 执行环境中就会创建变量对象;一般来说:函数调用完毕之后无论是执行环境(出栈),还是变量对象都是回收的。闭包的出现使得变量对象不回收;
(3)作用域链?
什么是作用域链:作用域链的本质是 指向变量对象的一组有序指针;字面上很难理解,我第一次看不明白!
有什么作用:在一个执行环境中对变量和方法进行有序(正确)访问;
什么时候创建: 一个函数声明的时候就创建了,作用域链会保存在一个函数的内部属性[[Scope]]中;
注意:执行流进入函数是: 创建执行环境->将作用域链(利用[[Scope]]属性) 复制到执行环境中->创建变量对象(活动对象)->将变量对象的引用(指针)导入作用域链的最前端->执行代码
具体请看下面的代码
function compare(value1,value2){
if(value1<value2){return 1;}
else if(value1>value2){return -1;}
else{return 0;}
}
var result=compare(5,10)//1
我们可以看到,作用域链是什么:有序指针,前面说的作用域最前端在就是0位置。 查找变量或者函数是最前端开始的,0指向的活动对象没有你要找的变量,再去1找。0-1其实
从代码的角度上看其实就是从函数内部一直向函数外部找标识符(变量或者函数),找到立即停止,不会再向上查找。这就是作用域链!
- 闭包
定义: 闭包是有权访问另外一个函数作用域变量的函数;注意闭包是一个函数!
创建闭包的主要的方法:在一个函数里面创建一个函数(闭包); 比如
<script type="text/javascript">
function A(value){
var num=value;
return function(){ //闭包
alert(num);
}
}
var B = A(0);
alert(B()); //0
</script>
在这里匿名函数是一个闭包,一般情况下: 一个函数执行完毕之后,无论是作用域链还是变量对象都会回收,但是这个匿名函数的作用域链里有A()变量对象的引用,所以没有消除。
还可以访问变量对象的num;这就是闭包的好处!但是闭包的出现加大内存负担,就这里而已,我们即使后面不再使用B函数了,A()变量对象也不会消失,javascript不知道你什么时候还会再用,当然我们可以这样 B=null; 这样的话匿名函数没有引用,被回收,A()变量对象也一起回收!
《javascript高级程序设计》中尼古拉斯大神建议我们:可以不使用闭包尽量不使用,万不得已才使用!
-
块级作用域
我们都知道javascript是没有块级作用域的,比如{}; if(i=0){} while(){} for(){}这些都不会形成块级作用域; 那么怎么创建 java c#类似功能的块级作用域?
语法:
(function(){
//块级作用域
})();
注意: 我们创建一个匿名函数,立即调用,里面的代码都要运行一遍,而在window中看不见,这不就是块级作用域吗? 还有一个好处,这个匿名函数没有指针,
调用后回收,不会产生垃圾(里面的方法,变量都不需要再访问的)。简直就是完美的块级作用域! 注意格式 (匿名函数)其实就是一个指针,再加上()就是调用了。
-
构造函数中的闭包
(1) 我们知道怎么为对象添加'私有变量' 这样
function Person(name,age){
this.name=name;//共有属性
this.age=age;
var school="一中"; //私有属性
this.GetSchool=function (){return school;}
}
我们这个school是私有的变量,因为闭包的原因, 对象实例自然可以访问这个变量; 比如 var p=new Person('nos',20); p.GetSchool(); // 一中;
(2)现在来一个奇葩: 静态私有属性 ;
(function(){
var school=""; //静态私有;
Person=function(name,age,value){ //构造函数
this.name=name;this.age=age;
school=value;
};
Person.prototype.GetSchool=function(){alert(school);}
})();
var p=new Person('andy',21,'一中');
p.GetSchool();//一中
var pp=new Person('nos',29,'二中');
pp.GetSchool();//二中
p.GetSchool();//二中
从结果上看 school是对象共有的,私有的属性, 即静态私有属性;
我们看看构造函数是怎么定义的: 没有使用var ,前面说过即使在函数里面定义,没有使用var申明,就是window的,为全局的。自然可以在全局使用!
这个匿名函数中调用了一次,只产生一个对象变量,school都是同一个,实现共享。