JavaScript难点总结
预编译/作用域链/闭包
- JavaScript引擎处理脚本的过程
- 预编译过程(第一次扫描)
- 创建全局对象GO(Global Object/window)
- 加载脚本文件
- 预编译
找到所有的变量声明,按照变量名加入GO,如果以存在,忽略。
找到所有的函数声明,按照变量名加入GO,如果以存在,替换。
非声明不参与预编译 - 解释执行(二次扫描)
脚本的预编译
- 没有var的变量,都不是变量声明,全部都认为是window的全局变量,不参与预编译
console.log(a);//报错,a is not defined
a = 5;
console.log(a);// 5
- 即使a在函数中,a也是全局变量,是运行时生效,不是预编译时生效
console.log(a);//报错,a is not defined
f();
function f(){
a = 5;
}
console.log(a);// 5
- 脚本中,所有的变量声明和函数声明,在脚本的预编译阶段完成,所有变量的声明与实际的书写位置无关
console.log(a);//undefined
var a = 5;
console.log(a);// 5
console.log(f);// function f
function f(){
console.log('here');
}
- 脚本中,如果变量与函数同名,函数可以覆盖变量,而变量不能覆盖函数
console.log(f);//function f
function f(){
console.log('here');
}
var f = 123;
- 脚本中, 如果有多个函数同名,最后声明的函数将覆盖所有前面的同名函数声明并且,忽略参数个数,也就是说,JS不支持重载
console.log(f);// function f(a,b)
function f(a){
console.log('here1');
}
function f(a,b){
console.log('here2');
}
-
函数调用
-
创建活动对象AO(Active Object)
-
预编译
scope chain
初始化arguments
初始化形参,将arguments中的值赋值给形参
找出所有的变量声明,按照变量名加入AO,如果已经存在,忽略。
找出所有的函数声明,按照函数名加入AO,如果已经存在,替换。
this初始化 -
解释执行
-
函数中,所有变量声明和函数声明,在函数的预编译阶段完成,所有变量的声明与实际的书写位
function f(){
console.log(a);//undefined
var a = 5;
console.log(a);//5
console.log(fin);//function fin
function fin(){
console.log('here');
}
}
f();
- 函数中,如果变量与函数同名,函数可以覆盖变量,而变量不能覆盖函数
function f(){
console.log(fin);//function fin
function fin(){
console.log('here');
}
var fin = 123;
}
f();
- 函数中, 如果有多个函数同名,最后声明的函数将覆盖所有前面的同名函数声明并且,忽略参数个数,(JS不支持重载)
function f(){
console.log(fin);// function f(a,b)
function fin(a){
console.log('here1');
}
function fin(a,b){
console.log('here2');
}
}
f();
- 当函数预编译后,遇到需要访问的变量或者函数,优先考虑自己AO中定义的变量和函数如果找不到,才会在其定义的上一层AO中寻找,直至到GO,再找不到才报错
var a1 = 123;
function f(){
console.log(a1);//123
}
f();
function f1(){
console.log(a)//报错,a is not defined
}
f1();
function f2(a){
console.log(a)//undefined
}
f2();
function f3(a){
console.log(a)//undefined
}
f3();
var a2 = 123;
function f4(){
function fin(){
console.log(a2);//123
}
fin();
}
f4();
function f5(a){
function fin(){
console.log(a);//undefined
}
fin();
}
f5();
function f6(a){
console.log(a)//5
var a = 6
console.log(a)//6
}
f6(5);
函数作用域 [[scope]]
-
执行环境(execution context):定义了执行期间可以访问的变量和函数。
-
作用域链:是AO和GO构成的链所谓执行环境,就是根据作用域链依次查找变量和函数:找到即停;全部找完无果,报错
-
每个函数在定义(函数声明\函数表达式)时会拷贝其父亲函数的作用域链;在函数被调用时,生成AO然后将AO压入作用域链的栈顶。
-
全局-预编译
1.Global EC (Object)
2.初始化 scope chain (Array)
3.Global EC的scope chain拷贝Global Object的地址
4.GO或者AO初始化 -
函数执行预编译过程
var g = 'g';
function fa(){
var a = 'a';
function fb(){
var b = 'b';
}
fb();
}
fa();
-
1~3
1.Global EC(Object)初始化 scope chain (Array)
2.Global EC的scope chain拷贝Global Object的地址
3.GO初始化this => window, g => undefined , fa => (function)
赋值g => ‘g’, 运行fa() -
4~7
4.生成fa 的 EC
5.初始化 fa的 scope chain
6.fa的scope chain拷贝父函数的作用域链,也就是GO
7.生成 fa 的 AO,将其压入栈顶 ,fa AO初始化this => window ,a => undefined , fb => (function)
赋值a => ‘a’, 运行fb() -
8~12
8.生成fb 的 EC
9.初始化 fb的 scope chain
10~11.fb的scope chain拷贝父函数的作用域链,也就是fa的scope chain[GO , fa AO]
12.生成 fb 的 AO,将其压入栈顶 ,fb AO初始化this => window ,b => undefined
赋值b => ‘b’
fb函数退出前,fb的AO引用次数1,fa的AO引用次数2,GO的引用次数313.fb函数退出,fb的EC被销毁,fb的scope chain释放,scope chain内的地址引用次数减一,fb的AO引用次数为0,fb的AO释放。
14.fa函数退出,fa的EC被销毁,fa的scope chain释放,scope chain内的地址引用次数减一,fa的AO引用次数为0,fa的AO释放。
闭包
- 闭包的定义:函数的AO通过scope chain相互连接起来,使得函数体内的变量都可以保存在函数的AO,这样的特性称为“闭包”。
- 闭包的危险:闭包会造成原有AO不释放,产生内存泄漏
- 闭包的应用
- 实现公有变量:1.缓存存储结构,2.封装,实现属性私有化,3.模块化开发,防止污染全局变量
- 闭包的预编译及原理
function outer(){
var scope = 'outer';
function inner(){
return scope;
}
return inner;
}
var fn = outer();
fn()
预编译过程:
1.Global EC(Object)初始化 scope chain (Array)
2.Global EC的scope chain拷贝Global Object的地址
3.GO初始化this => window 变量函数初始化, outer => (function),fn => undefined
fn => (function outer) 运行fn
4.生成outer的EC
5.初始化outer的scope chain
6.outer的scope chain拷贝父函数的作用域链,也就是GO
7.生成 outer的 AO,将其压入栈顶 ,outer AO初始化this => window, scope => undefined, inner => (function) return inner (实际上fn()= inner())
8.inner声明处,拷贝了outer的scope chain。outer运行结束,outer的EC被销毁,因为inner 拷贝的scope chain继续指向outer的AO,所以outer的AO不释放。
9.Global EC(Object)初始化 scope chain (Array)
10.Global EC的scope chain拷贝Global Object的地址
11.GO初始化this => window
12.生成inner的EC
13.初始化inner的scope chain
14.inner的scope chain拷贝父函数的作用域链,也就是outer的scope chain[GO, outer AO]
15.生成 inner的 AO,将其压入栈顶 ,outer AO初始化this => window
16.return scope
17.inner函数退出,inner的EC被销毁
因为outer的AO被inner拷贝,outer AO的引用次数为1,所以outer AO始终无法释放。
- 闭包的使用
- 公有变量
//累加器
function createCounter(){
var count = 0;
function counterAdd(){
count++;
console.log(count);
return count;
}
return counterAdd;
}
var counter = createCounter();
counter();//1
counter();//2
counter();//3
- 缓存存储结构, 多个变量,多个函数
//累加器
function createCounter(){
var count = 0;
function counterAdd(){
count++;
console.log(count);
return count;
}
function counterAddTwo(){
count += 2;
console.log(count);
return count;
}
function clearAction(){
count = 0;
console.log(count);
return count;
}
return [counterAdd, counterAddTwo, clearAction];
}
var counterAction = createCounter();
counterAction[0]();//1
counterAction[1]();//3
counterAction[2]();//0
counterAction[0]();//1
- 模块化
//累加器
function createCounter(x){
var count = 0;
var counter = {
counterAdd: function(){
count++;
console.log(x, count);
return count;
},
counterAddTwo: function(){
count += 2;
console.log(x, count);
return count;
},
clearAction: function(){
count = 0;
console.log(x, count);
return count;
}
};
return counter;
}
var counter = createCounter(1);
counter.counterAdd();// 1 1
counter.counterAddTwo();// 1 3
counter.clearAction();// 1 0
counter.counterAdd();// 1 1
var counter2 = createCounter(2);
counter2.counterAdd();//2 1
DOM/BOM
- 类的使用
//添加删除class
function addClassName(element, className){
// 1. 判断是否已经存在
var classNames = element.className.split(' ');
for(var i = 0; i < classNames.length; i++){
if(classNames[i] === className){
break;
}
}
//没找着
if(i === classNames.length){
if(element.className !== ''){
element.className += ' ' + className;
}else{
element.className = className;
}
}
// 找到,不用加
}
function removeClassName(element, className){
// 1. 判断是否已经存在
var classNames = element.className.split(' ');
for(var i = 0; i < classNames.length; i++){
if(classNames[i] === className){
break;
}
}
//找到
if(i !== classNames.length){
classNames.splice(i, 1);
element.className = classNames.join(' ');
}
// 没找到
}
- 用JS实现类似CSS中hover一样的功能
- onmuseover 鼠标悬停时触发(事件)
- onmuseout 鼠标离开后触发(事件)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<style type="text/css">
li {
list-style: none;
border: 1px solid #ccc;
background-color: #fff;
}
</style>
</head>
<body>
<ul>
<li>第零行</li>
<li>第一行</li>
<li>第二行</li>
<li>第三行</li>
<li>第四行</li>
<li>第五行</li>
</ul>
<script type="text/javascript">
var lis = document.querySelectorAll('li');
lis.forEach(function(element){
element.onmouseover = function(){
this.style.backgroundColor = '#ff0';
}
//鼠标触碰时backgroundColor变成黄色
element.onmouseout = function(){
this.style.backgroundColor = "#fff"
}
//鼠标离开后还原
});
</script>
</body>
</html>
- 提示文本框
- onfocus获得焦点
- onblur失去焦点
var body = document.querySelector('body');
body.innerHTML = '<input type="text" value="请输入搜索内容" id="txt" />';
document.querySelector('#txt').onfocus = function(){
if(this.value === '请输入搜索内容'){
this.value = '';
this.style.color = '#000';
}
}
//点击时文本框变成空
document<