<h4>本小节主要讲解闭包,是特别重要的一块内容.希望大家能够用心学习!</h4>
作用域链的补充内容 ----> 搜索规则
如果有忘记的人,翻一下上一篇博客
注意点:如果在内层作用域中声明了和外层作用域中同名的变量,那么这个变量是不会把外层的同名变量覆盖的
搜索原则:
- 在作用域中如果访问(读取|设置)某个变量,先在当前作用域中搜索,如果找到那么就直接使用
- 如果没有找到,那么就向上一级作用域中继续搜索,找到则使用,没有找到就重复上面的过程
- 直到0级作用域链
闭包
闭 : 关闭,封闭的意思,对外界不开方的
包 : 包装起来
闭包技术
- 作用域规则 : 内层的作用域可以访问外层的的作用域,但反过来不行
- 有的时候,我们确实需要访问一个封闭空间里的数据源(外层作用域 ----> 内层作用域)
- 就是提供一种间接访问封闭空间中私有数据的方法
访问数据
- 直接返回 return
- 闭包 对直接返回的数据进行包装(函数)
- 示例代码 :
<script>
function f1(){
var num = 10;
return num;
}
//num
var a = f1();
var b = f1();
console.log(a);
console.log(b);
//一次性获得数据
//1 每次调用函数获得的数据并不是同一份数据
//2 只能获取值,但是却不能修改值
</script>
- 示例代码 : 闭包一般写法
<script>
function f1(){
var num = 10;
return function(param){
num = param; //修改函数内部变量的值
return num;
};
}
</script>
- 示例代码 : 访问获取数据
<script>
function f1(){
var num = 10;
var name = "张学友";
return function(){
return [num,name];
};
}
var func = f1();
console.log(func()[0]);
console.log(func()[1]);
</script>
- 示例代码 : 闭包基本写法
<script>
function f1(){
var num = 10;
var name = "xxx";
return {
getNum:function(){
return num;
},
getName:function(){
return name;
}
}
}
var func = f1();
console.log(func.getName());
console.log(func.getNum());
</script>
闭包读取数据和设置数据
- 示例代码 : 获取数据
<script>
function foo(){
var name = "卡罗";
var age = 18;
return {
getName:function(nameValue){
//判断
if (nameValue != undefined)
{
name = nameValue;
}
return name;
},
getAge:function(ageValue){
age = ageValue;
return age;
}
}
}
var func = foo();
console.log(func.getName()); //
console.log(func.getName("卡罗尔"));
console.log(func.getName());
</script>
- 示例代码 :设置数据
<script>
function foo(){
var name = "卡罗";
var age = 18;
return {
getName:function(){
return name;
},
getAge:function(){
return age;
},
setName:function(nameValue){
name = nameValue;
},
setAge:function(ageValue) {
age = ageValue;
}
}
}
var func = foo();
console.log(func.getName());
func.setName("卡米尔");
console.log(func.getName());
func.setAge(20);
console.log(func.getAge());
</script>
闭包的作用
提供了一种间接访问函数封闭空间中数据的方法
- 访问函数内部的变量只能通过指定的借口
- 对变量的修改设置操作会更加安全,可以在设置之前对数据进行校验
- **延长变量的生命周期
**
<script>
function f1(){
var age = 20;
var author= "今何在";
return {
getAge:function(){
return age;
},
setAge:function(ageValue){
//校验处理
if (ageValue <0)
{
ageValue = 0;
}
//其他的处理(异常检测)
age = ageValue;
}
}
}
var func = f1();
func.setAge(22);
console.log(func.getAge());
//设置年龄 -3 (因为人的年龄不能是负值 所以得进行安全校验)
func.setAge(-3);
console.log(func.getAge());
</script>
setTimeout和闭包的执行
setTimeout(); 只调用一次函数(延迟函数) 一次是隔两秒(至少)
每隔一定的时候就调用一次
- **参数设置 : **
第一个参数:函数回调(时间到了之后执行的代码)
第二个参数:间隔时间(毫秒) 1秒 = 1000毫秒
setTimeout的问题
- 示例代码 :
<script>
for (var i = 0; i < 100; i++) {
setTimeout(function () {
console.log(i);
},0);
console.log("----");
}
</script>
解决setTimeout的问题
- 示例代码 :
<script>
for (var i = 0; i < 10; i++) {
setTimeout((function (j) {
return function () {
console.log(j);
}
})(i),10);
console.log("+++++++++");
}
</script>
div事件和闭包
- **示例代码 : **
<div>我是第1个div标签</div>
<div>我是第2个div标签</div>
<div>我是第3个div标签</div>
<div>我是第4个div标签</div>
<div>我是第5个div标签</div>
<div>我是第6个div标签</div>
<script>
var divs = document.getElementsByTagName("div");
for (var i = 0; i < divs.length; i++) {
divs[i].onclick = function(){
alert("我是第" + i + "标签");
}
}
// 每次点击得到的都是第六个标签
</script>
- **用闭包解决问题 : **
<script>
var divs = document.getElementsByTagName("div");
for (var i = 0; i < divs.length; i++) {
(function(a){
divs[a].onclick = function(){
alert("我是第" + a + "标签");
}
})(i)
}
</script>
函数的特性
- 函数本身也是对象(第一型的对象) 凡是可以使用对象的地方都可以使用函数
- 函数可以创建作用域
- 函数是可以被调用的
函数的约定和name值
函数声明
函数表达式
new 构造函数
示例代码 :
<script>
//function funName(参数1,参数2){函数体} //函数声明
//函数表达式(匿名)
var add = function(a,b){
console.log(a + b);
}
//函数表达式(命名)
var sum = function name(a,b){
console.log(a + b);
}
//sum.length // 形参的个数
console.log(add.name); //add 跟浏览器的实现有关系(在火狐中name的值为空)
console.log(sum.name); //name
//03构造函数创建对象
new Function(参数1,参数2,参数3)
</script>
函数的回调(作为参数传递)
函数是第一型对象(因此可以使用对象的地方都可以使用函数)
- 函数可以作为函数的参数
- 函数可以作为函数的返回值
回调函数(回调),当我们把某个函数作为参数传递给另一个函数的时候,这个函数就称为回调函数。
回调函数的基本模式
- 示例代码 :
<script>
function func(callBack) {
//处理其他的操作
callBack(); //调用回调函数
}
function demo() {
console.log("这是一个回调函数");
}
func(demo); //注意调用函数的时候,参数是回调函数的引用(不要加括号);
</script>
回调函数(关于this丢失的问题)
- 示例代码 :
<script>
//如果回调函数是某个对象的方法,而该对象方法中使用了this指针
//那么该方法作为回调函数来使用的时候,需要注意this丢失的问题
//01 提供一个对象,该对象中永远有showName方法
var obj = {
name:"xxx名字",
age:30,
showName:function () {
console.log(this.name);
},
showAge:function () {
console.log(this.age);
}
}
//02 提供一个函数,该函数接受一个参数(函数引用)
function demo(callBack) {
callBack();
}
demo(obj.showName); //注意:不要带上括号 打印结果为空(window.name)
demo(obj.showAge); //undefined
</script>
回调函数(解决this丢失的问题)
- 示例代码 :
<script>
//01 提供一个对象,该对象中永远showName方法
var obj = {
name:"默认的名字",
age:30,
showName:function () {
console.log(this.name);
},
showAge:function () {
console.log(this.age);
}
};
//02 提供一个函数,该函数接受一个参数(函数引用)
function demo(callBack,callBack_obj) {
if (typeof callBack == 'function')
{
callBack.call(callBack_obj);
console.log("++++");
}
}
demo(obj.showName,obj); //注意:不要带上括号
demo(obj.showAge,obj); //undefined
</script>
回调函数(兼容字符串方式)
- 示例代码 :
<script>
//01 提供一个对象,该对象中永远showName方法
var obj = {
name:"默认的名字",
age:30,
showName:function () {
console.log(this.name);
},
showAge:function () {
console.log(this.age);
}
};
//02 提供一个函数,该函数接受一个参数(函数引用)
function demo(callBack,callBack_obj) {
//处理第一个参数传递对象方法字符串的形式
if(typeof callBack == 'string')
{
callBack = callBack_obj[callBack];
}
if (typeof callBack == 'function')
{
callBack.call(callBack_obj);
}
}
//demo(obj.showName,obj);
//demo(obj.showAge,obj);
//传递字符串和对象来进行调用
demo("showName",obj);
</script>
函数作为返回值( 计算器)
- 示例代码 :
<script>
//使用闭包实现一个计数器(在该示例中setup函数的返回值为一个函数)
//通过调用返回值(一个函数),可以操作setup函数中的变量
var setup = function () {
var count = 0;
return function () {
return count ++;
}
}
var next = setup();
console.log(next()); //0
console.log(next()); //1
console.log(next()); //2
</script>
自定义函数( 惰性函数 )
惰性函数
- 某个函数直到第一次使用的时候才被正确的定义,并且其具有向后惰性,执行更少的工作。
应用场景
- 函数有一些初始化的准备工作要做,且只需要执行一次的情况。
特点
- 能够更新自己(函数)的实现。
缺点:
- 当重新定义自身的时候,已经添加到原始函数的任何属性都会丢失。
- 如果函数被赋值给了其他的变量或者是对象方法,那么在使用变量或者是对象方法调用时仍然会执行旧的函数体。
- 示例代码 :
<script>
var demo = function foo() {
console.log("foo!");
foo = function () {
console.log("new foo!");
}
}
//函数的调用
//foo(); //foo!
//foo(); //new foo!
//总结:该函数的实现特点在于能够更新自身的实现。
demo(); //foo!
demo(); //foo!
</script>
惰性定义函数存在的问题
- 添加属性
- 把函数赋值给新的变量
- 以对象的方法调用函数
- 示例代码 :
<script>
//01 声明函数foo
function foo() {
console.log("foo!");
foo = function () {
console.log("foo! foo!");
}
}
//02 为foo函数对象添加属性
foo.description = "foo函数的描述信息";
//03 把foo函数赋值给其他的变量
var func = foo;
//04 把foo函数赋值给对象中的方法
var obj = {
showFoo:foo
}
//05 验证并演示输出
func(); //foo!
func(); //foo!
console.log(func.description); //foo函数的描述信息
//总结:01 如果把函数赋值给其他的变量,那么在以其他变量的方式调用时不会更新自身,还是执行旧的函数体
obj.showFoo(); //foo!
obj.showFoo(); //foo!
console.log(obj.showFoo.description); //foo函数的描述信息
//总结:02 如果把函数赋值给对象的方法,那么在以对象方法形式调用时不会更新自身,还是会执行旧的函数体。
foo(); //已经更新过foo函数 foo! foo!
foo(); //已经更新过foo函数 foo! foo!
console.log(foo.description); //undefined
</script>
即时函数
在函数定义之后立即执行该函数。
即时函数模式的组成:
- 使用函数表达式来定义函数(匿名函数,注意不能使用函数声明方式)
- 在函数表达式末尾添加一组(),表示立即执行当前函数。
- 将整个函数包装在()中,有两种方式
即时函数的作用
- 用来将所有的代码包装到当前的作用域中,并且不会将任何的变量泄露到全局作用域中。
- js中没有代码块作用域,而函数是js中唯一可以创建作用域的。
- 即时函数就是利用了函数创建作用域这一点,来实现对一些需要封装且不允许外部访问的操作。
即时函数的优点
- 封装在一起执行的代码,且不会产生全局变量,在即时函数内部定义的所有变量都仅仅只是该函数的局部变量,不会造成全局变量污染问题。
- 具有更好的封装性,外部无法访问到该函数内部的数据。
- 示例代码 : 即时函数两种写法
<script>
//第一种写法
(function () {
console.log("即时函数的第一种写法");
}());
//第二种写法
(function () {
console.log("即时函数的第二种写法");
})();
</script>
- 示例代码 : 即时函数可以接受参数并提供返回值
<script>
//01 接受参数
(function (str) {
console.log(str); //hello
})("hello");
//02 提供返回值并赋值给新的变量
var foo = (function () {
return 2 + 1;
})();
console.log(foo); //3
</script>
即时函数和闭包
相同点:他们都是函数的一种特殊形态,并且可以共存。而且闭包配合即时函数“效果更佳”。
不同点:即时函数是定义一个函数,并立即执行。它只能被使用一次,相当于“阅后即焚”。闭包是指一个函数与它捕获的外部变量的合体,按照MDN的说法,闭包就像一个对象---一个具有一个方法(行为)和一个或多个私有字段(状态)的对象。从这个角度看,闭包是符合面向对象的封装思想的。
即时对象初始化
结构特征
- 提供一个对象,在该对象内部提供一个init初始化方法,使用()把对象包装起来(让字面量变成表达式)
- 然后随即调用init方法,完成初始化操作。
基本结构
({}).init();
模式优点
- 在执行一次性的初始化任务时保护全局的命名空间。
- 示例代码 :
<script>
({
name:"xxx",
age:23,
getDescript:function () {
console.log("名字:" + this.name + "年龄:" + this.age);
},
//注意:在对象中访问对象的属性和方法都需要使用this.前缀
init:function () {
this.getDescript();
//其他的初始化处理
}
}).init();