js闭包
开始理解:让函数内部变量暴露外部访问,默认作用域的原因,无法访问函数内部变量,Python装饰器 实现的是 传递变量到内部函数,按照作用域来说会找上层变量,让在执行函数之前执行其他动作,闭包体现?? 作用域:
let 块级作用域、函数作用域、全局作用域
闭包:一个函数访问另一个函数的内部变量
作用域链
function fun1() {
var a = 2
function fun2() {
console.log(a); //2
}
return fun2;
}
var result = fun1();
result();
- 闭包与作用域链 是不是冲突了?
由于有作用域链的概念,才能产生闭包这一特殊的对象, 函数嵌套 一个函数访问到另一个函数内部变量 形成闭包
闭包的定义
- 创建他的函数的上下文被销毁,但是他依然存在
- 在代码中引用了不是自身的参数或者局部变量
闭包是一种特殊的对象。
它由两部分组成。执行上下文(代号A),以及在该执行上下文中创建的函数(代号B)。
当B执行时,如果访问了A中变量对象中的值,那么闭包就会产生。
在大多数理解中,包括许多著名的书籍,文章里都以函数B的名字代指这里生成的闭包。而在chrome中,则以执行上下文A的函数名代指闭包。
当我们修改inner函数,返回上级作用域的outerName属性时,闭包就产生了
这里为什么会产生闭包呢?具体可以参考下方的图示
前面的全局入栈和outer函数入栈还是跟原来一样,但是当我们的outer函数入栈执行完毕准备出栈,准备被回收的时候,由于outName还被inner的作用域引用,不能被回收,产生了闭包。
即所谓的闭包就是通过函数调用,外部持有函数的句柄,让函数的空间不能消失。
产生的这块独体的空间永远存在,这块内存对外也是封闭的。所以就叫闭包。
2 常见问题分析
相信大家在面试的时候会经常问到这样的面试题。下面这段代码输入的是什么呢?
for(var i=1;i<=5; i++){
setTimeout(
function timer(){
console.log(i); },i*1000
);
}
这里输出的是5个6。需要解释这个问题呢,要涉及到js的的执行环境及作用域链了。
js的执行环境:JS是单线程环境,即代码的执行是从上到下,依次执行。
这样的执行称为同步执行。因为种种不要浪费和节约的原因。JS中引进了异步的机制。
在这里,for循环是同步代码,会先从上到下执行。
而setTimeout中的是异步代码会将其插入到任务队列当中等待。
因此在setTimeout执行的时候,for循环已经执行完成,i已经变成6。
作用域链。当setTimeout执行的时候,会向上去查找i的值。往上查找,
即for所在的作用域,已经是6了。因此6次setTimeout都会输出6。
那可能面试官会继续问,我们怎样才能依次输出1-5呢?这里就可以用到闭包来解决了。
for(var i=1;i<=5; i++){
(function(i) {
setTimeout( function timer(){
console.log(i);
} ,i*1000);
})(i)
}
我们将i作为参数传递,并且形成了一个新的立即执行函数作用域。
当setTimeout执行的时候,去查找i。即在立即执行函数作用域查找,此时的i我们可以根据上面一部分的分析,
形成了闭包之后,它的内存是不会消失的。因此这每次循环的时候都是当前i即1-5。
js特效案例介绍
- 图片切换
<img src="" id="flower" width='200px' height=200>
<br>
<button id="prev">上一张</button>
1.获取事件源 需要的标签
var flower = document.getElementById('flower');
var nextBtn = document.getElementById('next');
var prevBtn = document.getElementById('prev');
var minIndex = 1, maxIndex = 4; currentIndex = minIndex;
2.侦听按钮的点击
nextBtn.onclick = function(){
if(currentIndex === maxIndex){
currentIndex = minIndex;
}else{
currentIndex++;
}
flower.setAttribute('src','images'/image01.jpg);
}
prevBtn.onclick = function(){
if(currentIndex === minIndex){
//最后一张
currentIndex = maxIndex;
}else{
currentIndex--;
}
flower.setAttribute('src','images'/image01.jpg);
}
- 显示隐藏图片
1、获取事件源
var obtn = document.getElementById('btn');
var newimage = document.getElementByTagName('img')[0];
//var isshow = true;
2、绑定事件
obtn.onclick = function(){
3、事件驱动程序
// if(ishow){
if(obtn.innerHTML === '隐藏'){
newImg.style.display = 'none';
obtn.innerHTML ='显示';
// isshow = false;
}else{
newImg.style.display = 'black';
obtn.innerHTML ='隐藏';
// isshow = true;
}
}
- 衣服相册
获取事件源
var bigImg = document.getElementById('bigImg');
var smallImgs = document.getElementByClassName('smallImg');
for(var i = 0;i<smallImgs.length;i++){
// 2、遍历集合,给每个img标签添加事件
smallImgs[i].onmouseover = function(){
3、事件处理程序
//3.1在悬浮到每个li标签之前,先把所有的li标签的类名都置为空值
for(var j=0;j<smallImgs.length;j++){
smallImgs[j].parentNode.parentNode.setAttribute('class','');
}
// 3.2 修改大图的src属性值
var smallImgSrc = this.getAtttibute('src');
bigImg.setAttribute('src',smallImgSrc);
//3.3 在鼠标悬浮img标签的父标签添加类
this.parentNode.parentNode.setAttribute('class','active');
}
}
- 关闭小广告
<div id='qe_code'>
<img src='image/phone/.png' id='code'>
<span id='close'>X</span>
</div>
var closeSpan = document.getElementById('close');
var qe_code = document.getElementById('qe_code');
closeSpan.onclick = function(){
qe_code.style.display = 'none';
}
- 图片切换
初学者
function封装,
1.获取事件源
function $(id){
return typeof id === 'string' ? document.getElementById(id) : null;
}
function changebgcImg(liId,imgSrc){
2、添加事件
$(liId).onmouseover = function(){
3、改变背景图
$('box').style.background = imgSrc;
}
}
changebgcImg('item1,'url('images/big_pic1.jpg') no-repeat '');
changebgcImg('item2,'url('images/big_pic2.jpg') no-repeat '');
changebgcImg('item3,'url('images/big_pic3.jpg') no-repeat '');
完整版
function $(id){
return typeof id === 'string' ? document.getElementById(id): null;
}
var items = document.getElementsByClassName('item');
for(var i=0;i<items.length;i++){
// 采用index属性存储当前值 块的作用域
var item = items[i];
item.index = i + 1;
items[i].onmouseover = function(){
$('box').style.background = `url('images/big_pic${this.index}.jpg')` no-repeat
}
}
采用闭包实现:
- 百度换肤
overflow: hidden;
margin: 0 auto;
cursor: pointer;
margin: 10px 上 0 左 10px 下 96px 右; 顺时针
position:relative;
z-index : 10;
background-position: xx
background-position:
position: fixed;
获取对应的图片 同上 重点在于保存变量状态
var skin = document.getElementById('skin');
var allItems = document.getElementByTagName('li');
for (var i=0;i<allitems.length;i++){
item.index = allitems[i];
allitems[i].onclick = function(){
skin.style.backgroundImage = `url('images/skin${this.index}.jpg')`
}
}
- 千千音乐实现全选和反选
function $(id){
return typeof id === 'string' ? document.getElementById(id): null;
}
全选中
$('cancelSelect').onclick = function(){
for(var i=0;i<inputs.length;i++){
inputs[i].checked = true;
}
}
取消选中
$('cancelSelect').onclick = function(){
for(var i=0;i<inputs.length;i++){
inputs[i].checked = false;
}
}
反选
$('reverseSelect').onclick = function(){
for(var i=0;i<inputs.length;i++){
inputs[i].checked = !inputs[i].checked;
/*
if(inputs[i].checked){
inputs[i].checked = false;
}else{
inputs[i].checked = true;
}*/
}
}
- 表单验证
function $(id){
return typeof id === 'string' ? document.getElementById(id): null;
}
//input输入框失去焦点
$('score').onblur = function(){
//获取输入内容
var value = parseFloat(this.value);
console.log(typeof value);
// 验证
if(isNaN(value)){
//不是一个数
$('prompt').innerHTML = '输入的成绩不正确';
// $('prompt').setAttribute('class','error');
$('prompt').className = 'error';
this.style.borderColor = 'red' ;
}else if(value >=0 && value<=100){
合法的
$('prompt').innerHTML = '输入的成绩正确';
$('prompt').className = 'right';
this.style.borderColor = 'lightgreen' ;
}else{
超出成绩范围
$('prompt').innerHTML = '输入的成绩正确';
$('prompt').className = 'error';
this.style.borderColor = 'red' ;
}
}
// input 输入框获取焦点 恢复原来的状态
$('socre').onfocus = function(){
$('prompt').innerHTML = '请输入您的成绩';
$('prompt').className = '';
$('score').style.borderColor = 'darkgray';
$('score').style.outline = 'none';
$('score').value = '';
}
正则表达式 == 表单验证
上传图片验证
使用正则比较简单
jpg png gif jpeg
window.onload = function(){
1、获取标签
var file = document.getElementById('file');
2、侦听图片选择的变化
file.onchange = function(){
2.1 获取上传图片路径
var path = this.value;
console.log(path);
2.2获取. 在路径字符串中占的位置
var loc = path.lastIndexOf(',');
console.log(loc);
2.3 截图 文件路径的后缀名
var suffix = path.substr(loc);
2.4 转小写
var lower_suffix = suffix.toLowerCase();
2.5判断
if(lower_suffix === '.jpg' || lower_suffix===',png' || lower_suffix === '.jpeg' || lower_suffix==='.gif')
alert('上传图片格式正确');
}else{
alert('上传图片格式错误');
}
}
}
- 随机验证码校验
window.onload = function(){
保存全局,与新输入的验证码进行校验
var code;
1、获取对应的标签
var codeDiv = document.getElementById('code');
var newCodeInput = document.getElementById('newCode');
var validate = document.getElementById('validate');
加载页面获取对应的验证码
createCode()
1.获取min到max之间的整数(1-100)
function random(max,min){
return Math.floor(Math.random() * (max - min ) + min );
}
function createCode(){
设置默认空的字符串
code = "";
设置长度
var codeLength = 4;
var randomCode = [0,1,2,3,4,5,6,7,8,9,'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R', 'S','T','U','V','W','X','Y','Z'];
for(var i =0 ;i<codeLength;i++ ){
设置随机范围 0-36
var index = random(0,36);
code += random[index];
}
codeDiv.innerHTML = code;
}
验证按钮校验
validate.onclick = function(){
获取用户输入的验证码
var newCode = newCodeInput.value.toUpperCase();
if(newCode === code){
验证成功 跳转对应网址
window,location.href = 'https://www.baidu.com';
}else{
验证失败
alert('验证码不正确')
清空输入框
newCodeInput.value = '';
创建验证码
createCode();
}
}
- 发布评论
function $(id){
return typeof id === 'string' ? document.getElementById(id): null;
}
window.onload = function(){
监听按钮的点击
$('btn').onclick= function(){
获取用户输入的内容
var content = $('commit').value;
console.log(content);
判断
if(content.length ===0 ){
alert(‘请输入内容');
return;
}
创建li标签插入到ul中
var newLi = document.createElement('li');
newLi.innerHTML = '${content}<a href = 'javascript:void(0)'>删除</a>';
// $('comment_content').appendChild(newLi);
$('comment_content').insertBefore(newLi,$('comment_content').children[0]);
清空输入框中的内容
$('comment').value = ' ';
删除评论
var delBtns = document.getElementsByTagName('a');
for(var i=0; i<delBtns.length;i++){
delBtns[i].onclick = function(){
this.parentNode.remove();
}
}
}
}
- 九宫格
获取标签
var btns = document.getElementsByTagName('button');
var items = document.getElementsByClassName('item');
监听按钮的点击
btns[0].onclick = function(){
循环
for(var i =0;i<items.length;i++){
items[i].style.float = 'left';
items[i].parentNode.style.width = (3*items[i].offsetWidth) + 'px'
}
}
改造:
btns[0].onclick = function(){
mjj_flex(3)
}
btns[1].onclick = function(){
mjj_flex(4)
}
btns[2].onclick = function(){
mjj_flex(5)
}
function mjj_flex(colsNum){
for(var i =0;i<items.length;i++){
items[i].style.float = 'left';
items[i].parentNode.style.width = (colsNum*items[i].offsetWidth) + 'px'
}
九宫格布局定位实现 子绝父相
第0行第0列 top:row * h left: col * w
第0行第1列 top:0 * h left: 1 * w
第0行第2列 top:0 * h left: 2 * w
第1行第0列 top:1 * h left: 0 * w
第1行第1列 top:1 * h left: 1 * w
function mjj_flex(colsNum){
for(var i=0;i<items.length;i++){
求每个盒子占得的行数和列数 10 3行 1列
11 3行 2列
var row = parseInt(i/colsNum);
var col = parseInt(i % colsNum);
设置盒子定位
items[i].style.position = 'absolute';
items[i].style.top = (row * items[i].offsetHeight) + 'px';
items[i].style.left = (col * items[i].offsetWidth) + 'px';
}
}
- 日期特效
1.获取标签
var nowDate = document.getElementById('nowDate');
var day = document.getElementById('day');
用定时器 更新时间的变化
setInterval(nowNumTime.10);
function newNumTime(){
var now = new Date();
var hour = new.getHours();
var minute = now.getMinutes();
var second = new.getSeconds();
var year = now.getFullYear();
var month = now.getMonth();
var d = now.getDate();
var week = now.getDay();
var weeks = ['星期日','星期一','星期二','星期三','星期四','星期五','星期六']
var temp = ' ' + (hour > 12 ? hour - 12 : hour);
if(hour === 0){
temp = '12';
}
temp = temp + (minute < 10 ? ':0' : ":") + minute;
temp = temp + (second < 10 ? ':0' : ":") + second;
temp = temp + (hour < 12 ? 'P.M' : "A.M");
temp = '${year}年${month}月${d}日 ${temp} ${weeks[weel]}';
nowDate.innerHTML = temp;
}
- 定时器回顾
获取标签
var start = document.getElementById('start');
var stop = document.getElementById('stop');
var num = 0 , timer = null;
start.onclick = function(){
使用定时器的时候 先清除定时器 在开启定时器,防止用户频繁性的开启定时器
clearInterval(timer);
timer = setinterval(function(){
num++;
console.log(num);
},1000)
}
stop.onclick = function(){
clearInterval(timer);
}
transform 应用 过渡 位移 旋转 缩放比例 倾斜 10度
transform: translate(100px.200px) ratate(10deg) scale(2.0) skew(10deg);
window.onload = function(){
var btn = document.getElementById('btn');
var box = document.getElementById('box');
var index =0 ;
# 模板字符串 or 字符串拼接 注意 ``
btn.onclick = function(){
index++;
box.style.transform = 'translate(${index * 100}px,${index*50}px) ratate(${index*10}deg) scale(${index}*1.3)';
}
}
- 数字时钟案例
获取标签
var hour = document.getElementById('hour');
var second = document.getElementById('second');
var minute = document.getElementById('minute');
开启定时器 获取当前时间
setInterval(function(){
获取当前的时间戳
var now = new Date();
获取小时 分钟秒
var s = now.getSeconds();
var m = now.getMinutes() + s/60;
var h = now.getHours() % 12 + m/60;
旋转
second.style.transform = 'rotate(${s*6}deg)';
minute.style.transform = 'rotate(${m*6}deg)';
hour.style.transform = 'rotate(${h*30}deg)';
},10);
长图滚动案例
获取标签
var box = document.getElementById('box');
var pic = document.getElementByTagName('img')[0];
var divTop = document.getElementById('top');
var divBottom = document.getElementById('bottom');
添加事件
var num=0 ,timer=null; # 全局变量
divTop.onmouseover = function(){
clearInterval(timer);
让图片向上滚动
timer = setInterval(function(){
num -=10;
if (num >= -3666){
pic.style.top = num + 'px';
}else{
clearInterval(timer);
}
},50)
}
divBottom.onmouseover = function(){
clearInterval(timer);
让图片向下滚动
timer = setInterval(function(){
num +=10;
if (num <= 0){
pic.style.top = num + 'px';
}else{
clearInterval(timer);
}
},50)
}
box.onmouseout = function(){
clearInterval(timer);
}
闭包解决for循环完成后得到结果显示 一个函数变量引用另一个函数变量,形成闭包, 上层函数不会释放,直到内层函数引用执行完毕,在去销毁外层函数变量,垃圾回收