一、函数的闭包
1、函数的执行&作用域链
作用域:全局作用域:全局可用,但是容易被污染
和函数作用域:一次性的,用了就会释放
函数的执行原理:
1、程序加载时,创建执行环境栈ESC,保存函数调用的顺序的一个数组;首先会压入全局执行环境(全局EC),全局EC引用着全局对象window,window中保存着全局变量
2、定义函数时:函数会封装着函数定义的内容,在函数对象中定义出scope(作用域)属性,全局函数的作用域都是window
3、调用函数前:在执行环境栈ESC压入新的EC(函数的EC),然后回创建一个活动对象AO,AO保存着调用函数时使用的局部变量,并在函数EC中添加scope chain(作用域链)引用着AO;AO的parent属性为函数的scope引用的对象。
4、调用时:函数就会优先使用自己的局部变量,没有的话再通过parent属性引用的函数的scope引用的对象(全局)
5、调用完时:函数的EC会出执行环境栈ESC,AO也就没了,局部变量也就释放了
作用域链(scope chain):以EC中的scope chain属性为起点,经过AO逐级引用,形成的一条链式结果就称之为作用域链
2、函数的闭包:
闭包:希望保护一个可以反复使用的局部变量的一种词法结构;这个局部变量就可以反复使用,并且不会被污染;但是受保护的变量永远不会被释放,过多使用闭包会导致内存泄露(就是一直占用着内存)
1、两层函数进行嵌套,
2、外层函数创建出一个受保护的变量,并且要return出内层函数
3、内层函数操作受保护的变量,并return出结果
function cs(){
var i=0;
return function(){
i++;
return i;
}
}
闭包一般用在“防抖节流”上,体现在三个事件上:
var h1 = document.getElementsByTagName('h1')[0];
var inp = document.getElementsByTagName('input')[0];
//鼠标移入事件
h1.onmousemove = function () {
f2();
}
var f2=f1();
function f1() {
//声明了变量未赋值,所以是一个undefined
var timer;
return function () {
//判断timer,undefined为false
if (timer) {
//假的就清空定时器
clearTimeout(timer)
}
//用timer创建一个定时器
timer = setTimeout(function () {
//操作dom树的代码,会间隔1s才会执行
return h1.innerHTML = parseInt(h1.innerHTML) + 1
}, 1000)
}
}
//inp事件
inp.oninput=function(){
a2();
if(inp.value.length>=5){
inp.style.borderColor="red";
}
}
var a2=a1();
function a1(){
var timer;
return function(){
if(timer){clearTimeout(timer)}
timer=setTimeout(function(){
return inp.vlaue;
},50)
}
}
//屏幕尺寸变化事件
window.onresize=function(){
rs();
}
function fdjl(){
var timer;//保存定时器的地方
return function(){
//判断有没有定时器,有的话就清空
if(timer){clearTimeout(timer)}
//在开启一个定时器
timer=setTimeout(function(){
if(innerWidth>996){
console.log(1);
d1.style.background="pink";
}else{
d1.style.background="purple";
}
},1000)
}
}
var rs=fdjl();
window.onresize()
固定用法:
function fdjl(){
var timer;//保存定时器的地方
return function(){
//判断有没有定时器,有的话就清空
if(timer){clearTimeout(timer)}
//在开启一个定时器
timer=setTimeout(function(){
//自己的操作
},1000)
}
}
var rs=fdjl();
elem.on事件名=function(){
rs();
}
二、object
面向过程:先干什么再干什么,逐步写下去
面向对象:对象有属性和方法
面向对象:三大特点:封装 继承 多态
1、面向对象的封装的创建方式:
直接量方式:var obj={"属性名":属性值,"方法名":function(){方法的操作}}
构造函数方式:var obj=new Object();
创建的是空对象,需要添加:obj.属性名=属性值;obj.方法名=function(){};
自定义构造函数方式:可以同时创造多个对象
function h52104(name,age,hobby){
this.xingming=name;//
this.nianling=age;//
this.aihao=hobby;
}
var zhl=new h52104("张啊",18,"听歌猛男");
var ddk=new h52104("啊",12,"哔哔");
var zhn=new h52104("在",28,"测过");
console.log(zhl);
console.log(zhn)
console.log(ddk)
封装的使用案例:
var xxk={
//创建一个属性,这个属性是dom树中所有的li,
"lis":document.getElementsByTagName('li'),
"ds":document.getElementsByTagName('div'),
//创建一个方法:
"init":function(){
//这个方法中再次调用了一个方法,this指当前的这个对象
//这个方法对象中没有,所以要创建一个
this.bind();
},
//创建一个方法
"bind":function(){
var me=this;
//this还是值当前对象,lis是一个集合,需要循环才能分别绑定事件
for( var i=0;i<this.lis.length;i++){
this.lis[i].onclick=function(){
//这里如果还用this的话,this指向的是绑定事件的元素了
//所以上面用一个变量保存住对象的this,用对象在调用一个方法
//这里调用的时候必须把当前点击事件的this做实参
me.animate(this);
}
//当前this还是指当前的对象,给当前对象的属性lis绑定事件
}
},
//这里的方法要有个形参,接住当前点击事件的元素this
"animate":function(li){
//先循环把所有的class名清空
for(var i=0;i<this.lis.length;i++){
this.lis[i].firstElementChild.className="";
this.lis[i].className="";
}
//这里不能循环来获取点击事件的元素,只能用形参通过实参把点击事件的元素保存起来
//不然不能单独操作点击事件的元素
li.firstElementChild.className="jh";
li.className="a1";
}
}
xxk.init();
对象中this的指向:
1、单个元素绑定事件:this->这个元素
2、多个元素绑定事件:this->当前元素
3、*函数中出现了this:this->谁调用的此方法,this就是谁
4、定时器中this永远指向window
5、构造函数中的this -> 当前正在创建的对象