作用域链中变量的使用原则
- 在作用域链中使用(读取/设置)变量的时候, 首先在当前作用域链中查找, 找到就直接使用
- 如果没有找到, 就去上一级作用域链中查找, 直到0级作用域
闭包
-
一般情况下作用域: 内层作用域可以访问外层作用域, 反之不行
function f1(){ var n=999; function f2(){ alert(n); // 999 } }
-
有时候需要从外层作用域访问内层作用域
-
直接return, 一次性获取数据, 每次获取的不是同一份数据(变量创建后使用后就销毁了)
function f1(){ var n=999; function f2(){ alert(n); } return f2; } var result=f1(); result(); // 999
-
闭包技术: 可以间接访问封闭空间私有数据的方法
- 闭包就是对直接return的数据进行包装(函数)
-
-
闭包获取数据和设置数据
- 判断不传形参的情况, 形参和变量不能同名 (同名的话, 形参在调用赋值时, 会先从当前作用域找同名变量)
闭包的好处
- 获取数据只能通过指定的方法(接口)
- 在设置数据的时候更加安全, 可以做一些校验工作
- 延长变量的生命周期
setTimeout和闭包的执行
//第一种写法
for(var i=0; i<3; i++){
(function(index){
setTimeout(function(){
console.log(index+'+++');
},0);
})(i);
}
//第二种写法
for(var i=0; i<3; i++){
setTimeout((function(index){
return function(){
console.log(index+'----');
}
})(i),0);
}
//第三种方法
for(let i=0; i<3; i++){
setTimeout(function(){
console.log(i+'----');
},1000);
}
div事件和闭包
-
JS的任务
- 渲染任务
- 代码的主要任务
- 事件性的任务(点击,定时器..)
-
JS是单线程
- 进程: 正在运行的应用程序 (工厂)
- 线程: 进程中用来执行任务的(工人),一个线程同一时间只能执行一个任务
- 串行执行: 多个任务一个一个的按顺序执行
- 并发执行: 多个任务同时执行
- 多程线:多条线程
- 多线程的原理: 1s = 1万个0.0001s cpu在多个任务之间来回的快速切换,造成多个任务同时执行的假象
//JS单线程 先循环完 再触发点击事件时 i遍历完自加1变成5了--所以当点击div时变成5了
for (var i = 0; i < divs.length; i++) {
divs[i].onclick = function () {
console.log('点击了第' + i + '个div');
}
}
//方法一:闭包解决拿到i的值
for (var i = 0; i < divs.length; i++) {
(function (j) {
divs[j].onclick = function () {
console.log('点击了第' + j+ '个div');
}
})(i);
}
//方法二
for (var i = 0; i < divs.length; i++) {
divs[i].onclick = (function (j) {
return function () {
console.log('点击了第' + j + '个div');
}
})(i);
}
函数的特殊性
- 特殊性(特点): 函数本身是对象, 且对象可以提供作用域
- 函数可以在运行时动态的创建, 还可以在程序执行过程中创建
- 函数可以赋值给变量, 可以被扩展, 甚至删除
- 函数可以作为其他函数的参数/返回值
- 函数可以拥有自己的属性和方法
- 注意
-
{}
块在JS中不会创建作用域, 哪怕是if或者是while语句中使用var关键字申明的变量也并非局部变量, 函数是可以通过()调用并执行对象
-
- 函数是第一类型对象
函数可以向普通对象一样, 作为函数的参数 | 赋值给变量(函数表达式) | 作为函数的返回值返回
-
fun.name
函数名可以获取, 但不能修改//函数作为参数传递 setTimeout(function () { console.log(1); },100); //函数作为返回值 function func() { return function () { console.log("demo"); } } var f = func(); f(); //demo //函数赋值给变量 var a = function(){} a(); //直接通过变量的名称调用函数
回调函数
- 定义: 把一个函数作为其他函数的参数
- 把函数作为另一个函数的参数
//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);
//(01)以上代码传入两个参数,分别为具体的回调函数,和该回调函数所属的对象
//(02)该函数的参数接受两种方式的回调传递(一种是直接传递函数引用,一种是直接以字符串的方式传递对象方法的字符串)
//(03)在函数内部对传入的回调参数做处理,修正this的问题
- 函数作为返回值
- 使用闭包实现一个计数器(在该示例中setup函数的返回值为一个函数)
- 通过调用返回值(一个函数),可以操作setup函数中的变量
<script>
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>
惰性函数
-
定义: 函数真正的内容需要执行一次函数才能确定, 实现自我更新
function foo() { console.log("foo!"); //函数是引用型数据, foo的存储地址被重新定义了 foo = function () { console.log("new foo!"); } } //函数的调用 //foo(); //foo! //foo(); //new foo!
应用场: 函数有一些初始化的准备工作要做,且只需要执行一次的情况。
-
注意点
- 在函数上添加的属性或方法, 自我更新后无法访问
-
把惰性函数赋值给变量, 以变量的(对象的方法)方式来调用, 不会更新(调用仍然是同一份数据)
即时函数
-
定义: 在函数定义之后立即执行该函数。
//第一种写法 (function () { console.log("即时函数的第一种写法"); })(); //第二种写法 ;(function () { console.log("即时函数的第二种写法"); })(); //补充写法 (function (a) { console.log(a); }(20)); +function (b) { console.log(b); }(30); -function (b) { console.log(b); }(40);
-
模式组成
- 使用函数表达式来定义函数(匿名函数, 不能使用函数声明方式)
- 在函数表达式末尾添加一组(), 表示立即执行当前函数
- 将整个函数包装在()中, 有两种方式
-
作用
- 用来将所有的代码包装到当前的作用域中,并且不会将任何的变量泄露到全局作用域中。
- js中没有代码块作用域,而函数是js中唯一可以创建作用域的。
- 即时函数就是利用了函数创建作用域这一点,来实现对一些需要封装且不允许外部访问的操作。
-
优点
- 不会产生全局变量,在即时函数内部定义的所有变量都仅仅只是该函数的局部变量,不会造成全局变量污染问题。
- 具有更好的封装性,外部无法访问到该函数内部的数据。
-
即时函数的传参和返回值
//01 接受参数 (function (str) { console.log(str); //hello })("hello"); //02 提供返回值并赋值给新的变量 var foo = (function () { return 2 + 1; })(); console.log(foo); //3
即时对象初始化
- 结构特征
- 提供一个对象, 在该对象内部提供一个init初始化方法
- 使用()把对象包装起来(让字面量变成表达式)
- 然后随即调用init方法, 完成初始化操作
- 基本结构
- 即时对象初始化:
({init: function(){}}).init();
- 即时对象初始化:
- 模式优点
- 在执行一次性的初始化任务时保护全局的命名空间
({
name:"张三",
age:23,
getDescript:function () {
console.log("名字:" + this.name + "年龄:" + this.age);
},
//注意:在对象中访问对象的属性和方法都需要使用this.前缀
init:function () {
this.getDescript();
//其他的初始化处理
}
}).init();
设计模式: 为了解决开发中遇到的一类问题而提出的一套方法
- 要求: 一般一套系统需要设计模式
- 来源: 建筑行业
- 工厂模式核心
- 提供一个父构造函数-->开了一家工厂
- 设置父构造函数的原型对象-->产品公共东西
- 在父构造函数上提供一个静态的工厂方法-->生产产品
- 接受传入的参数
- 判断是否支持生产
- 设置子构造函数的原型对象是父构造函数的一个实例(原型链继承)
- 利用子构造函数创建对象并且返回
- 定制合作伙伴
- 利用父构造函数的静态工厂方法创建对象
- 作用
- 通过统一的方法/接口创建对象, 并且根据不同的类型创建不同的对象, 便于代码的维护和扩展