<!DOCTYPE html><html> <head> <meta charset="UTF-8"> <title>闭包</title> <style type="text/css"> div { width: 100px; height: 100px; background: lightgreen; float: left; margin: 20px; font: 30px/100px "microsoft yahei"; text-align: center; } </style> </head> <body> <div>a</div> <div>b</div> <div>c</div> <div>d</div> <div>e</div> <div>f</div> <div>g</div> <div>h</div> <div>i</div> <div>j</div> <script type="text/javascript"> var divs=document.getElementsByTagName("div"); for (var i=0;i<divs.length;i++) { divs[i].onclick=function(){ alert(i); } } </script> </body></html>运行结果:
因为点击事件的函数内部使用外部的变量i一直在变化,当我们指定click事件时并没有保存i的副本,这样做也是为了提高性能,但达不到我们的目的,我们要让他执行的上下文保存i的副本,这种机制就是闭包。
修改后的代码:
<!DOCTYPE html><html> <head> <meta charset="UTF-8"> <title>闭包</title> <style type="text/css"> div { width: 100px; height: 100px; background: lightgreen; float: left; margin: 20px; font: 30px/100px "microsoft yahei"; text-align: center; } </style> </head> <body> <div>a</div> <div>b</div> <div>c</div> <div>d</div> <div>e</div> <div>f</div> <div>g</div> <div>h</div> <div>i</div> <div>j</div> <script type="text/javascript"> var divs=document.getElementsByTagName("div"); for (var i=0;i<divs.length;i++) { divs[i].onclick=(function(n){ return function(){ alert(n);} })(i); } </script> </body></html>运行结果:
n是外部函数的值,但是内部函数(点击事件)需要使用,返回函数前的n被临时驻留在内存中给点击事件使用,简单说就是函数的执行上下文被保存起来,i生成了多个副本。
1.2、理解闭包
闭包概念:当一个内部函数被调用,就会形成闭包,闭包就是能够读取其他函数内部变量的函数,定义在一个函数内部的函,创建一个闭包环境,让返回的这个子程序抓住i,以便在后续执行时可以保持对这个i的引用。内部函数比外部函数有更长的生命周期;函数可以访问它被创建时所处的上下文环境。
Javascript语言特有的"链式作用域"结构(chain scope),子对象会一级一级地向上寻找所有父对象的变量
二、对象
对象就是“键/值”对的集合并拥有一个连接到原型(prototype)对隐藏连接。
2.1、对象常量(字面量)
一个对象字面量就是包含在一对花括号中的零个或多个“键/值”对。对象字面量可以出现在任何允许表达式出现的地方。
对象的定义:
//空对象 var obj1={}; //对象中的属性 var obj2={name:"foo",age:19}; var obj3={"nick name":"dog"}; //对象中的方法 var obj4={ price:99, inc:function(){ this.price+=1; } }对象中可包含的内容:
//对象中可包含的内容 var obj5 = [{ name: "jack" }, { name: "lucy", //常量 hobby:["读书","上网","代码"], //数组 friend:{name:"mark",height:198,friend:{}}, //对象 show:function(){ //函数 console.log("大家好,我是"+this.name); } }]; //对象中的this是动态的,指向的是:调用者 obj5[1].show();输出:大家好,我是lucy
//3取值 var obj6={"nick name":"pig",realname:"Rose"}; console.log(obj6.realname); //console.log(obj6.nick name); 错误方法二:使用索引器,当对象中的key有空格是
//3取值 var obj6={"nick name":"pig",realname:"Rose"}; console.log(obj6["realname"]); console.log(obj6["nick name"]);2.3、枚举(遍历)
var obj7={weight:"55Kg","nick name":"pig",realname:"Rose"}; for (var key in obj7) { console.log(key+":"+obj7[key]); }运行结果:
输出顺序是不能保证的。
2.4、更新与添加
如果对象中存在属性就修改对应值,如果不存在就添加。对象通过引用传递,它们永远不会被复制
var obj8={realname:"King"}; obj8.realname="Queen"; //修改 obj8.weight=1000; //添加属性 obj8.show=function() //添加方法 { console.log(this.realname+","+this.weight); } obj8.show();输出:
var obj8={realname:"King"}; obj8.realname="Queen"; //修改 obj8.weight=1000; //添加属性 obj8.show=function() //添加方法 { console.log(this.realname+","+this.weight); } obj8.show();//引用 var obj9=obj8; //obj9指向obj8的引用 obj9.realname="Jack"; obj8.show();输出:
2.5、对象的原型
javascript是一种动态语言,与C#和Java这样的静态语言是不一样的;javascript并没有严格的类型,可以简单认为javascript是由对象组成的,对象间连接到原型(prototype)实现功能的扩展与继承。每个对象都链接到一个原型对象,并且可以从中继承属性,所有通过常量(字面量)创建的对象都连接到Object.prototype,它是JavaScript中的顶级(标配)对象,类似高级语言中的根类。
现在我们修改系统中的Object对象,添加一个创建方法,指定要创建对象的原型,实现类似继承功能:
<script type="text/javascript"> if(typeof Object.beget !== "function") { Object.create = function(o) {//构造函数,用于创建对象var F = function() {};//指定由构造函数创建的对象的原型F.prototype = o;//调用构造方法创建新对象return new F(); } } var rose={ name:"rose", show:function(){console.log("姓名:"+this.name); } }; rose.show(); //输出 var lucy=Object.create(rose); //简单认为是:创建一个对象且继承rose lucy.name="lucy"; //重写 lucy.show(); </script>运行结果:
原型关系是一种动态关系,如果修改原型,该原型创建的对象会受到影响。
var lucy=Object.create(rose); //简单认为是:创建一个对象且继承rose lucy.name="lucy"; //重写var jack=Object.create(rose); jack.name="jack";//修改原型中的方法 rose.show=function(){ console.log("姓名->"+this.name); }lucy.show(); jack.show();结果:
关于原型在函数中会再讲到。
2.6、删除
//删除属性 delete mark.name;//调用方法,输出:姓名:undefined mark.show();//删除函数 delete mark.show;//错误,mark.show is not a function mark.show();删除不用的属性是一个好习惯,在某些情况下可能引发内存泄漏。
var name="foo"; //name是全局的,被暴露 i=1; //全局的,没有var关键字声明的变量是全局的,与位置关系不大 function show(){ //show 是全局的,被暴露 console.log("name->"+name); console.log(++i); } //i是全局的 2 show();//3 show();
封装后:
//对外只暴露bar,使用闭包封装 var bar=function(){ var i=1; return{name:"bar",show:function(){console.log("name->"+this.name);console.log(++i);} }; };var bar1=bar(); //2 bar1.show(); //3 bar1.show();var bar2=bar(); //2,因为被封装,且闭包,i是局部私有的 bar2.show();运行结果:
三、函数
javascript中的函数就是对象,对象就是“键/值”对的集合并拥有一个连接到原型对隐藏连接。
3.1、参数对象 (arguments)
第一个函数中有一个默认对象叫arguments,类似数组,但不是数组,该对象是传递给函数的参数。
<script type="text/javascript"> function counter(){ var sum=0; for(var i=0;i<arguments.length;i++){sum+=arguments[i]; } return sum; } console.log(counter(199,991,1,2,3,4,5)); console.log(counter()); </script>运行结果:
function f1() { console.log(arguments.length); f2=function() {console.log(arguments.length); } return f2; } var f=f1(1,2,3); f();运行结果:
<script type="text/javascript"> /*构造函数*/ //可以简单的认为是一个类型的定义 function Student(name,age){this.name=name;this.age=age;this.show=function(){console.log(this.name+","+this.age);} } //通过new关键字调用构造函数,创建一个对象tom var rose=new Student("rose",18); var jack=new Student("jack",20);rose.show(); jack.show(); </script>
3.3、函数调用
3.3.1、call
调用一个对象的一个方法,以另一个对象替换当前对象call([thisObj[,args])
hisObj 可选项。将被用作当前对象的对象。args 将被传递方法参数序列。
call 方法可以用来代替另一个对象调用一个方法。call 方法可将一个函数的对象上下文从初始的上下文改变为由 thisObj 指定的新对象。
示例:
/*构造函数*/ function Student(name,age){this.name=name;this.age=age; } show=function(add){console.log(add+":"+this.name+","+this.age); } //通过new关键字调用构造函数,创建一个对象tom var rose=new Student("rose",18); var jack=new Student("jack",20); //调用show方法,指定上下文,指定调用对象,this指向rose,“大家好是参数” show.call(rose,"大家好"); show.call(jack,"Hello");运行结果:
call方法中的参数都可以省去,第1个参数表示在哪个对象上调用该方法,或this指向谁,如果不指定则会指向window对象。
示例:
var name="无名"; var age=18; show.call();结果:
apply([thisObj[,argArray]])
/*构造函数*/ function Student(name,age){this.name=name;this.age=age; } show=function(greeting,height){console.log(greeting+":"+this.name+","+this.age+","+height); } //通过new关键字调用构造函数,创建一个对象tom var rose=new Student("rose",18); var jack=new Student("jack",20); //调用show方法,指定上下文,指定调用对象,this指向rose,“大家好是参数” show.apply(rose,["大家好","178cm"]); show.apply(jack,["Hello","188cm"]);运行结果:
从上面的示例中可以发现apply的第2个参数是一个数组,数组中的内容将映射到被调用方法的参数中,如果单这样看发现不如call方便,其实如果直接取方法的参数arguments则apply要方便一些。通过简单的变化就可以替代call。
function display(){ show.apply(jack,arguments); } display("hi","224cm");结果:
hi:jack,20,224cm
function add() { console.log("add被调用"); //add方法的调用函数,如果调用add方法的不是函数则为null console.log(add.caller); } function calc(){ add(); } //直接调用add方法 add();//间接通过calc方法调用 calc();运行结果:
caller与this还是有区别的,this是指调用方法的对象,而caller是指调用函数的函数。
<script type="text/javascript"> function add(n) { console.log("add被调用"); if(n<=2){ return 1; } return add.caller(n-1)+add.caller(n-2); } function calc(n){ console.log("calc被调用"); return add(n); } //1 1 2 console.log(calc(3)); </script>结果:
3.3.4、Callee
当函数被调用时,它的arguments.callee对象就会指向自身,也就是一个对自己的引用
function add(n1,n2){console.log(n1+n2);//arguments.callee(n1,n2); //指向add方法return arguments.callee; } add(1,2)(3,4)(5,6)(7,8)(8,9);运行结果:
当第1次调用add方法时输入3,立即将函数返回再次调用,每次调用后又返回自己,这样可以实现链式编程。
3.5、立即执行函数表达式 (IIFE)
IIFE即Immediately-Invoked Function Expression,立即执行函数表达式
3.5.1、匿名函数与匿名对象
匿名函数就是没有名称的函数,javascript中经常会使用匿名函数实现事件绑定,回调,实现函数级的私有作用域,如下所示:
function(){ console.log("这是一个匿名函数"); };匿名对象:
{ name:"foo", show:function(){ console.log(this.name); } }没有名称的匿名函数也叫函数表达式,它们间是有区别的。
function Identifier ( Parameters ){ FunctionBody }
function(){ console.log("这是一个匿名函数"); };结果:
b)、函数表达式(Function Expression)function Identifier(Parameters){ FunctionBody }
函数表达式中,参数和标识符都是可选的,与函数定义的区别是标识符可省去。
其实,"function Identifier(Parameters){ FunctionBody }"并不是一个完整的函数表达式,完整的函数的表达式,需要一个赋值操作。
比如: var name=function Identifier(Parameters){ FunctionBody }
3.5.3、立即执行函数表达式与匿名对象
//1 正常定义函数 function f1(){ console.log("正常定义f1函数"); }; //2 被误解的函数表达式 function(){ console.log("报错Unexpected token ("); }(); //3 IIFE,括号中的内容被解释成函数表达式 (function(){ console.log("IIFE,正常执行"); })(); //4 函数表达式 var f2=function(){ console.log("这也被视为函数表达式"); };第3种写法为什么这样就能立即执行并且不报错呢?因为在javascript里,括号内部不能包含语句,当解析器对代码进行解释的时候,先碰到了(),然后碰到function关键字就会自动将()里面的代码识别为函数表达式而不是函数声明。
<!DOCTYPE html><html> <head> <meta charset="UTF-8"> <title>IIFE</title> </head> <body> <script type="text/javascript"> //调用匿名函数 (function() { console.log("这是一个函数表达式"); })(); //调用匿名对象 ({ name: "foo", show: function() {console.log(this.name); } }).show(); console.log({ a: 1 }.a); console.log({ a: function() {} }.a()); </script> </body></html>运行结果:
3.5.4、各种IIFE的写法
//最常用的两种写法(function(){ /* code */ }()); // 老师推荐写法(function(){ /* code */ })(); // 当然这种也可以// 括号和JS的一些操作符(如 = && || ,等)可以在函数表达式和函数声明上消除歧义// 如下代码中,解析器已经知道一个是表达式了,于是也会把另一个默认为表达式// 但是两者交换则会报错var i = function(){ return 10; }();true && function(){ /* code */ }();0, function(){ /* code */ }();// 如果你不怕代码晦涩难读,也可以选择一元运算符!function(){ /* code */ }();~function(){ /* code */ }();-function(){ /* code */ }();+function(){ /* code */ }();// 你也可以这样new function(){ /* code */ }new function(){ /* code */ }() // 带参如果是函数表达式,可直接在其后加"()"立即执行。
3.5.5、参数
函数表达式也是函数的一种表达形式,同样可以像函数一样使用参数,如下所示:
(function (n){ console.log(n); })(100);输出:100
(function(win,undfd){ win.console.log("Hello"==undfd); })(window,undefined);3.5.6、添加分号
var k=100 (function (n){ console.log(n); })(k);上面的脚本会报错,因为javascript解释器会认为100是函数名。
var k=100 ;(function (n){ console.log(n); })(k);这样就正确了,在javascript中一行语句的结束可以使用分号,也可以不使用分号,因为一般的自定义插件会使用IIFE,这是一段独立的代码,在应用过程中不能保证用户会加上分号,所以建议在IIFE前加上分号。
function(w, d, $) { }(window, document, window.jQuery);3)、避免冲突
var StuObj = { getStu: function(name) { return new Student(name); }}/*构造函数*/function Student(name) { this.name = name; this.show = function() { console.log("Hello," + this.name); }}Common.js
function other1() {}function other2() {}(function($) { if($) { $.getStu("Tom").show(); }})(typeof StuObj=="undefined"?false:StuObj);A.HTML
<!DOCTYPE html><html> <head> <meta charset="UTF-8"> <title>A</title> </head> <body> <script src="js/Student.js" type="text/javascript" charset="utf-8"></script> <script src="js/common.js" type="text/javascript" charset="utf-8"></script> </body></html>B.HTML
<!DOCTYPE html><html> <head> <meta charset="UTF-8"> <title></title> </head> <body> <script src="js/common.js" type="text/javascript" charset="utf-8"></script> <script type="text/javascript"> other1(); </script> </body></html>3.5.8、IIFE的变形
(function(n){ console.log(n);//认为这里有30000代码}(100));如果中间有很长的代码,参数100只有到文档的末尾才可以看得到,变形后的结果:
(function(exp){ exp(100); }(function(n){ console.log(n); //认为这里有30000代码 }));修改后的代码中有两个函数表达式,一个作为参数,就是我们主要要完成的功能向控制台输出数字,另一个作来IIFE立即执行的函数,主要的功能函数变成的IIFE的参数了。
(function(win, doc, $) { }(window, document, jQuery)); ( function(library) {library(window, document, window.jQuery); } (function(window, document, $) { }) );bootstrap的写法:
+function(yourcode) { yourcode(window.jQuery, window, document); }(function($, window, document) {$(function() {}); //jQueryDOM加载完成事件 });结合call或apply的写法:
(function(x){console.log(x)}).call(window,888); (function(x){console.log(x)}).apply(window,[999]);输出:888 999