JS难点合集

预编译/作用域链/闭包

  • 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的引用次数3

    13.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<
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值