第三章 函数作用域和快作用域
隐藏内部实现
function doSomething(a){
b = a + doSomethingElse(a * 2)
console.log(b * 3)
}
function doSomethingElse(a){
return a - 1
}
var b
doSomething(2) //15
在这个代码片段中,doSomethingElse函数和变量b都是函数doSomething的私有内容,应该放到函数doSomething中。
function doSomething(a){
function doSomethingElse(a){
return a - 1
}
var b
b = a + doSomethingElse(a * 2)
console.log(b * 3)
}
doSomething(2) //15
规避冲突
function foo(){
function bar(a){
i = 3 ;//修改for循环中所属作用域的i
console.log(a + i)
}
for(var i = 0;i < 10;i++){
bar(i * 2); //造成无限循环
}
}
foo()
这个代码片段中,由于bar函数中的i会导致for循环的i进行变化,从而导致无限循环(将var换成let可行)
全局命名空间
模块管理
函数作用域
可以通过包装函数,将内部变量和函数定义“隐藏”起来,外部作用域无法访问包装函数内部的任何内容
var a = 2
function foo(){ // 《--添加这一行
var a = 3
console.log(a); //3
}// 《--添加这一行
foo()// 《--添加这一行
console.log(a); //2
通过这种包装函数foo将内部的变量a和外部变量a给隔离开
需要添加三行才可以实现隔离
var a = 2;
(function foo(){// 《--添加这一行
var a = 3
console.log(a); //3
})()// 《--添加这一行
console.log(a); //2
上面这个代码片段中foo被绑定在函数表达式自身中而不是所在的作用域的,也就是说
(function foo(){...})作为函数表达式意味着foo只能在 .. 所代表的位置中所访问。
匿名和具体
setTimeout(function(){
console.log("I wailted one second")
},1000)
建议给匿名函数加上名字,比如timeoutHandler,这样子可以方便调试、理解代码、引用自身
setTimeout(function timeoutHandler(){
console.log("I wailted one second")
},1000)
立即执行函数表达式
var a = 2;
(function foo(){// 《--添加这一行
var a = 3
console.log(a); //3
})()// 《--添加这一行
console.log(a); //2
第一个()将函数编程表达式,第二个()执行了这个函数
var a = 2;
(function IIFE(global){
var a = 3
console.log(a); //3
console.log(global.a);//2
})(window)
console.log(a); //2
对这个函数IIFE将全局变量window传递进入,更换变量名字,然后通过global.a获取到函数外的变量a = 2
应用场景
undefined = true; //绝对不要这么做
(function IIFE(undefined){
var a;
if(a === undefined){
console.log("Undefined is safe here")
}
})();
这样子可以保证函数IIFE中的undefined是正确的而不是外面那个被篡改的,因为没有传入参数所以函数IIFE中的参数就是undefined
var a = 2;
(function IIFE(def){
def(window);
})(function def(global){
var a = 3
console.log(a); //3
console.log(global.a); //2
})
这里有两个函数IIFE和def,IIFE的传入参数是函数def,然后通过def(window),将全部变量传入进入,然后改换名字为global,从而调用变量外部变量a
块作用域
for(var i = 0;i < 10;i++){
console.log(i)
}
console.log(i)
在这里的话,在for循环中使用var i,会使得i泄露,然后外部可以进行调用
try/catch
try{
undefined(); //执行一个非法操作来制造一个异常
}
catch (err){
console.log(err); //能够正常执行
}
console.log(err);//"ReferenceError: err is not defined
let
let关键字可以将变量绑定到所在的任意作用域
let会在所在的作用域中执行。
{
console.log(bar); //不会提升
let bar = 2;
}
垃圾收集
let循环
var foo = true;
if(foo){
var a = 2;
const b = 3;//包含在if中的块作用域常量
a = 3;//正确
// b = 4;//错误
}
console.log(a);//3
console.log(b);//"ReferenceError: b is not defined
第四章 提升
先有鸡还是先有蛋
a = 2;
var a;
console.log(a); //2
输出的结果是2,因为使用var变量会让变量a进行提升
console.log(a) //undefined
var a = 2
输出的结果是 undefined
编译器
对于变量
对于变量的话,编译器在执行的时候,首先会先定义声明,接着才是赋值声明。
对于函数
函数声明会提升
function f(){...}
在其作用域中可以调用
foo();
function foo(){
console.log(a);
var a = 2;//undefined
}
foo();
函数表达式不会提升
let fun = function f(){...}、
fun不会被提升
f();//"TypeError: f is not a function,表明被提升了,但是此时函数f = function还没有赋值完成
foo();//ReferenceError
var f = function foo(){
console.log(a);
var a = 2;//undefined
}
f();//undefined
函数优先
函数声明和变量声明都会被提升,但是顺序是,函数先被提升然后才是变量
foo();
function foo(){
console.log(1);
}
foo = function(){
console.log(2);
};
function foo(){
console.log(3);
}
输出的结果是3,函数声明重复以最后的3为最新的值,函数声明先于函数表达式
foo();//"TypeError: foo is not a function 表示没有提升
var a = true
if(a){
function foo(){
console.log("a")
}
}
else{
function foo(){
console.log("b")
}
}
foo();//"b"
避免在块内部声明函数
第五章 作用域闭包
function foo(){
var a = 2;
function bar(){
console.log(a);
}
bar();
}
foo();
console.log(a);//"ReferenceError: a is not defined,说明a没有到全局变量上
清晰地展现闭包:
function foo(){
var a = 2;
console.log(a)
function bar(){
console.log(a);
}
return bar
}
// foo()
var baz = foo(); //把内部函数的地址传递给它
baz(); //2,朋友这就是闭包
另外一种形式
function foo(){
var a = 2;
function baz(){
console.log(a);//2
}
bar(baz);
}
function bar(fn){
fn();//快看,这就是闭包
}
foo();//这里用了一个内部的函数,这个内部就是foo函数内部的一个函数
把内部寒湖是baz传递给bar,当调用这个内部函数时fn
var fn
function foo(){
var a = 2;
function baz(){
console.log(a)
}
fn = baz;//将baz分配给全部变量
}
function bar(){{
fn();//快看,这就是闭包
}}
foo()
bar();//2
进一步了解
function wait(message){
setTimeout(function timer(){
console.log(message)
},1000)
}
wait("你好呀")
就将一个内部函数timer传递给setTimeout(..)。timer具有涵盖wait(..)作用域的闭包。所以可以对变量message进行引用。
只要使用了回调函数,实际上就是在使用闭包!!!
循环和闭包
for(var i = 1;i <= 5;i++){
console.log("hello");//验证setTimeout是一个异步函数
setTimeout(function timer(){
console.log(i);
},i*1000)
}
在这里的话,输出的时5次每隔1s输出6;应为在这个函数中变量i是一个共享变量,所以说for循环中的话,会先将函数setTimeout()函数先打印出来,然后再进行赋值。因为他是一个异步函数。
改进
for(var i = 1;i <= 5;i++){
(function(j){
setTimeout(function timer(){
console.log(j);
},i*1000);
})(i);
}
迭代中每次使用IIFE都会生成一个新的作用域,使得延迟函数的回调可以将新的作用域封闭在每隔迭代内部,每隔迭代中都会含有一个具有正确值的变量供我们使用。
重返块作用域
for(let i = 1;i <= 5;i++){
console.log("hello");//验证setTimeout是一个异步函数
setTimeout(function timer(){
console.log(i);
},i*1000)
}
将原来的var i = 1转化成let i = 1
块作用域和闭包联手便可以天下无敌
模块
function CoolModule(){
var something = "cool"
var another = [1,2,3]
function doSomething(){
console.log(something)
}
function doAnother(){
console.log(another.join("!"))
}
return {
doSomething: doSomething,
doAnother: doAnother
};
}
var foo = CoolModule()
foo.doSomething();//cool
foo.doAnother();//1!2!3
这个模式再JavaScript中称为模块,创建一个函数CoolModule(),这个对象类型的返回值看作本质上是模块的公共API,可以通过访问API中的属性,foo.doSomething()
- 必须有外部的封闭函数
- 封闭函数必须返回至少一个内部函数、
转成成单例模式
var foo = (function CoolModule(){
var something = "cool"
var another = [1,2,3]
function doSomething(){
console.log(something)
}
function doAnother(){
console.log(another.join("!"))
}
return {
doSomething: doSomething,
doAnother: doAnother
};
})();
foo.doSomething();//cool
foo.doAnother();//1!2!3
\
公共API
var foo = (function CoolModule(id){
function change(){
//修改公共API
publicAPI.identify = identify2;
}
function identify1(){
console.log(id)
}
//变成大写
function identify2(){
console.log(id.toUpperCase());
}
//公共的API
var publicAPI = {
change:change,
identify:identify1
};
return publicAPI
})("foo module");
foo.identify();//"foo module"
foo.change();
foo.identify();//"FOO MODULE"
通过模块实例的内部保留对公共API对象的内部引用,可以从内部对模块实例进行修改,包括添加或删除方法和属性,以及修改他们的值
现代的模块机制
var MyModules = (function Manager(){
var modules = {};
function define(name,deps,impl){
for(var i = 0;i < deps.length;i++){
deps[i] = modules[deps[i]]
}
//为了模块的定义引入了包装函数(可以传入任何依赖),并且将返回值,,也就是模块的API,存储在一个根据名字来管理的模块列表中
modules[name] = impl.apply(impl,deps);
}
function get(name){
return modules[name]
}
return {
define:define,
get:get
}
})()
MyModules.define("bar",[],function(){
function hello(who){
return "Let me introduce: " + who;
}
//返回一个属性
return {
hello:hello
}
})
MyModules.define("foo",["bar"],function(bar){
var hungry = "hippo";
function awesome(){
//对hello属性进行大写操作
console.log(bar.hello(hungry).toUpperCase());
}
return {
awesome:awesome
}
})
//得到对应的函数
var bar = MyModules.get("bar")
var foo = MyModules.get("foo")
console.log(
bar.hello("hippo")
);//"Let me introduce: hippo"
foo.awesome();//"LET ME INTRODUCE: HIPPO"
模块的两个主要特征:
- 为创建内部作用域而调用了一个包装函数
- 包装函数的返回值必须直到包括一个对内部函数的引用,这样子就会创建涵盖整个包装函数内部作用域的闭包
动态作用域
function foo(){
console.log(a);//2
}
function bar(){
a = 3;//如果这里是var a = 3;foo()的输出是2,不然就是3
console.log(a)
foo()
}
var a = 2
bar()
可以使用语法作用域来理解,使用var a = 3的话就说明作用域被限制再bar()中了