全局变量与局部变量、闭包
定义在function外部的变量:全局变量
定义在function内部的变量(有var):局部变量
定义在function内部但没有var的变量:全局变量;想要在全局中使用,必须先调用一次方法
使用场景:
全局变量:少用,一直常驻内存中,不易被销毁,容易出现命名冲突,适合公用的变量
局部变量:函数执行完即被销毁,无法被持久化
作用域
函数作用域:函数内部使用
全局作用域:整个网页范围
块级作用域:{},在整个大括号内
let的特点:
-
let增加了块级作用域,就是大括号{}
-
不能在初始化之前去访问let修饰的变量:暂时性死区
-
用let定义的全局变量不属于window,用var定义的全局变量属于window。
//暂时性死区
function run() {
//不能在初始化之前去访问let修饰的变量:暂时性死区
console.log(x); //Cannot access 'x' before initialization
let x = 66;
}
run()
//let定义的变量不属于window
var global_name = 'frank'
let global_age = 19
//用let定义的全局变量,不属于window,用var定义的全局变量是属于window的
console.log(window.global_name);
console.log(window.global_age);
const的特点:
命名:要求全部大写,多个单词之间用_隔开
-
定义一个常量,不能被修改的
-
必须在定义的时候进行初始化
-
如果将const当成局部变量使用,则特点和let一样。
//常量:定义的时候就得初始化它的值,且后面不能再修改
const IP_ADDRESS = '172.168.2.1';
// IP_ADDRESS="127.0.0.1"; 错误的:Assignment to constant variable.对常量变量的赋值。
//如果要将const当成局部变量去使用,则特点和let一样的
if(1==1){
const ONE=1;
}
console.log(ONE); //报错:ONE is not defined
但是如果用const定义一个对象,是可以修改对象内部属性的。
不能修改整个对象
const OBJ = {name:'jack',age:20}
OBJ.name = 'rose';
console.log(OBJ);
//OBJ中的name属性已经被修改为rose
OBJ = {name:'rose',age:18}//这样修改会报错
var的特点:
-
没有块级作用域,只有个全局作用域和局部作用域
-
可以提前声明
-
属于window
一个经典题目
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
for (var i = 1; i <= 3; i++) {
//定时器异步执行,等待外部代码执行完毕后执行定时器。
setTimeout(function() {
alert(i);
}, 1000)
} //弹出三个弹框,结果分别为4,4,4
for (let i = 1; i <= 3; i++) {
//let在每次循环的时候都创建l副本,将i的值保存了下来。
setTimeout(function() {
alert(i);
}, 1000)
} //弹出三个弹框,结果分别为1,2,3
//即使定时器的时间变化,运行的结果也不会变化
</script>
</body>
</html>
IIFE(立即执行函数表达式)
语法:
(function([形参]){
//函数体
[return]
}([实参]))
作用:
- 早期模块化解决方案,避免了命名冲突。
- 防止外部代码来访问我内部的一些变量,提高了安全性。
闭包
函数嵌套函数,内部函数可以访问外部函数的局部变量。闭包环境是内部函数和外部函数沟通的桥梁。
var fn = (function(){
var num = 10;
return function(){
num++;
console.log(num);
}
})()
fn();//11
fn();//12
//在外部执行fn,不论执行多少次,num变量一直存在,从未还原过。
//好处:num既有了全局变量的常驻内存不销毁的特点,并且num又不会和外部名称重名。
缺点:不销毁,会增大内存消耗,不会被垃圾回收器回收,容易造成内存泄漏。
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
</ul>
<script>
let lis = document.querySelectorAll('li');
for (var i=0;i<lis.length;i++){
(function(s){
lis[s].onclick = function(){
alert(s);
}
})(i)
}
</script>
垃圾回收机制(GC)
一个变量没在任何地方被使用,会被垃圾回收器收走,释放内存空间。
如何让GC回收num?
fn = null//将fn设置为null,没有地方使用num,则会被GC收走
arguments
当前函数的实参列表
arguments.callee
当前函数(只能在函数内使用)
call&&apply
function play(a,b){
console.log(this,a+b)
}
play();this为window
var person{};
//动态改变当前函数的this指向,改成person
play.call(person,1,2);//调用函数,参数传递采用序列方式
play.apply(person,[1,2]);//调用函数,参数传递采用数组方式
相同点:都是调用函数并且修改函数里面的this值
不同点:
函数名.call(this新值,参数1,参数2,…,参数N)
函数名.apply(this新值,[参数1,参数2,…,参数N])
函数柯里化
把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数。
function sum(){
//调用slice方法,但是把slice里面的this改成arguments,然后产生一个新数组
let args = [].slice.call(arguments);//1,3
function exec(){
args.push(...arguments);// args = [1,2,3,4]
return exec;
}
exec.calc=function(){
return args.reduce(function(total,current){
return total+current;
})
}
return exec;
}
console.log(sum(1,3)(2,4)(100)(1).calc());
//slice截取的时候,找的是 this
let args = [7,8,9].slice();//还是7,8,9 从原数组截取的
let args = [7,8,9].slice.call([1,2,3]);//1,2,3,从后面这个数组截取的