模块五 前端JavaScript实战案例


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属性时,闭包就产生了

01

  • 这里为什么会产生闭包呢?具体可以参考下方的图示

  • 前面的全局入栈和outer函数入栈还是跟原来一样,但是当我们的outer函数入栈执行完毕准备出栈,准备被回收的时候,由于outName还被inner的作用域引用,不能被回收,产生了闭包。

02

即所谓的闭包就是通过函数调用外部持有函数的句柄让函数的空间不能消失
产生的这块独体的空间永远存在这块内存对外也是封闭的所以就叫闭包
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 ;  顺时针

positionrelative;
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列  toprow * h left: col * w
第0行第1列  top0 * h left: 1 * w
第0行第2列  top0 * h left: 2 * w
第1行第0列  top1 * h left: 0 * w
第1行第1列  top1 * 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循环完成后得到结果显示 一个函数变量引用另一个函数变量,形成闭包, 上层函数不会释放,直到内层函数引用执行完毕,在去销毁外层函数变量,垃圾回收