脚本宝典收集整理的这篇文章主要介绍了JS三部曲,变量提升,this与作用域,闭包,脚本宝典觉得挺不错的,现在分享给大家,也给大家做个参考。
这篇文章总结下之前看的文章和自己在工作中遇到的坑,纯手写的,有什么写的不对的请多多提出修正哈
变量提升
何为变量提升?js里面的变量在声明之前就可以使用,因为该声明已经被提升至该作用域(函数或全局)的顶部
直接上代码
function fn1(){
console.log(a)
var a=10;
function a(){};
console.log(a)
}
fn1();
//var和function都会变量提升,优先级function>var,上面可理解为
function fn1(){
var a;
function a(){};
console.log(a);//a函数
a=10;
console.log(a);//10
}
这时我们加上一个参数来比较
function fn1(a){
console.log(a);
var a=20;
function a(){};
console.log(a);
}
fn2(10);//打印出函数a
//变量提升有个优先级:函数a>参数>变量,上面可理解为
function fn2(a){//a=10
var a;
//a被参数的a赋值为10或者理解为后声明的覆盖不了前面已赋值的
function a(){};//变量a变为函数a
console.log(a);//函数a
a=20;//a重新赋值为20
console.log(a);//20
}
作用域
作用域有全局作用域和函数作用域,我的理解其实就是变量(标识符)作用域,当执行一段代码时会在所在作用域解析变量和函数,该作用域变量会覆盖外围的作用域,该作用域找不到的标识符会沿着作用域链向上延伸查找,找不到就报错;
var a = 10;
!function(){
console.log(a);
var a= 20;
console.log(a);
}();
console.log(a);
//上面理解为
var a = 10;//全局作用域
!function(){
var a;//函数作用域
console.log(a);//undefined,a向上查看-》函数作用域-》全局作用域
a= 20;
console.log(a);//20
}();
console.log(a);//10,查找到全局作用域,函数作用域不可见
再来看一个例子
console.log(b)
if('b' in window){
var b=10;
console.log(b);
};
console.log(b);
//ES3,ES5中if..else,with,while,for,switch等等是没有作用域的,
//只有全局作用域和函数作用域,如下
var b;
console.log(b);//undefined
if('b' in window){
b =10;
console.log(b);//10
};
console.log(b);//10
闭包
每个人都有不同理解,我的理解是闭包就是让函数闭不了包,外部变量的值被缓存,内部变量可访问外部变量,也可以说是外部变量可访问内部变量,用法不同说法就不同,闭包在一些简单例子上可以代替new实例化的开销,用自执行函数先把该执行的执行完
var Fn =(function(){
var obj = {};
obj.id=10;
return {
obj:obj
};
})();
来看一个最常用的淘宝tab栏切换例子
<ul>
<li>素颜</li>
<li>断桥残雪</li>
<li>最佳歌手</li>
</ul>
var tab = document.querySelectorAll('ul>li');
for(var a=0;a<tab.length;a++){
tab[a].onclick=function(){
console.log(a);
}
};
//当点击tab每一项时候,我们会很惊奇地发现打印出来的都是tab.length,
//由于for循环的时候a不会进去函数里面,等循环结束后,当你点击都已变为
//tab.length,我们需要把a缓存起来
第一种
for(var a=0;a<tab.length;a++){
tab[a].index=a;
tab[a].onclick=function(){
console.log(this.index);
}
};
console.log(a);//tab.length,这个下面第三种方法会说
//这种是传统做法,把一个自定义属性绑定在每个dom节点li上,增大dom的开销
第二种
for(var a=0;a<tab.length;a++){
!function(a){
tab[a].onclick=function(){
console.log(a);
}
}(a);
};
//或者
for(var a=0;a<tab.length;a++){
tab[a].onclick=(function(a){
return function(){
console.log(a);
}
})(a);
};
console.log(a);//tab.length
//这种是闭包写法,使用自执行函数缓存起每个a,当然闭包也会有内存泄漏,
//性能等其它问题,用的适当还是可以的
第三种
for(let a=0;a<tab.length;a++){
tab[a].onclick=function(){
console.log(a);
};
};
console.log(a);//a is not defined
//这是es6的写法,一个let搞定所有问题,原生的方法,推荐使用;
//大家发现第二个a打印'a is not defined',
//但是第一种和第二种方法就可以访问到a为tab.length,这有可能导致变量
//泄漏或者冲突,let就很好的解决了这个问题,那是因为es6新增
//了块级作用域,外部访问不到for的作用域
this
this指向执行时所在的作用域,一般为window和函数,node环境为global, 来看个例子就明白了
var id=10;
var obj = {
id:100,
show:function(){
console.log(this.id);
console.log(this.name);
}
}
var b = obj.show;
b();
obj.show();
//obj.show先保存起来后在调用时,这时是直接调用一个函数b,函数的this
//指向window,注意window.name是window默认就有的为空,结果就是10 ''
//obj.show()方式是直接调用,this对象指向obj,name为undefined,
//结果就是100 undefined
this中的bind,call,apply
bind,call,apply可以改变当前函数的作用域,bind不一定要立即执行函数,call,apply必须立即执行函数 来看一个构造函数继承的例子
var Father = function(){
this.name='张三';
this.age=50;
//以下三种都可以
var son = Son.bind(this);//指向的对象
son('葫芦娃',10);//间接调用,也可以直接调用
//Son.apply(this,['葫芦娃',10]);//第一个参数是指向的对象,第二个参数是数组
//Son.call(this,'葫芦娃',10);//第一个参数是指向的对象,后面分开写
}
Father.PRototype.show=function(){
console.log('爸爸叫'+this.name);
}
var Son = function(name1,age1){
this.name1=name1;
this.age1=age1;
}
var father = new Father;//通过改变Son构造函数的this指向为Father
console.log(father);//{name: "张三", age: 50, name1: "葫芦娃", age1: 10}
来看个bind的例子
var obj = {};
obj.show = function () {
function _show() {
console.log(this)
};
return _show.bind(obj);
}();
obj.show();
// 打印obj对象,由于先声明赋值了,自执行函数后,函数_show的this
//指向被bind方法改为obj
var obj = {
show: function () {
function _show() {
console.log(this)
};
return _show.bind(obj);
}()
};
obj.show();
//打印window对象,没事先声明赋值,这里obj变量提升,自执行函数后,
//bind里面的obj为undefined,this为undefined的默认指向window
箭头函数:默认指向所在的宿主对象,也就是上一级对象,而不是执行时的对象,基于这个this指向上一级的特殊性,我们在某些情况下就不需要缓存this的值,直接使用;
var obj = {
id:100,
show:function(){
(()=>{
console.log(this)
})();
setTimeout(()=>{
console.log(this)
},1);
},
show1:()=>{
console.log(this);
}
};
var obj1 = obj.show;.
obj1();//window,箭头函数上一层是个普通函数,普通函数this指向window
obj.show();//obj,箭头函数上一层作用域的this指向obj
obj.show1();//window,this指向上一级即window
箭头函数与普通函数的混合嵌套
var obj = {
show:function(){
setTimeout(fn);
function fn(){
console.log(this);
setTimeout(()=>{
console.log(this);
setTimeout(()=>{
console.log(this);
})
})
};
},
show1:function(){
setTimeout(fn.bind(obj));
function fn(){
console.log(this);
setTimeout(()=>{
console.log(this);
setTimeout(()=>{
console.log(this);
})
})
};
}
};
obj.show();//都是window,最外面的定时器是普通函数,普通函数this指向window,每个箭头函数this指向上一层
obj.show1();//都是obj,最外面的定时器this指向被改变为obj,每个箭头函数this指向上一层
综合题
第一道
var a = 1;
!function(){
var a = 10;
function fn(){
this.a += this.a;
a+=a;
console.log(this.a);
console.log(a);
};
var obj = {
a:5,
show:fn
};
obj.show();
var obj1 = obj.show;
obj1();
}();
//这里最主要是考查this指向,去年面试时候做到的笔试题,自己加以改进
//'obj.show()'直接调用,此时函数fn的this指向obj,
//this.a就是5,this.a累加后就是10,根据就近原则,变量a会
//沿着作用域链向上查找,找到上一层的10就停止了,
//a累加后就是20
//'var obj1 = obj.show;'这步保存起普通函数,普通函数被调用时
//this指向就是window//,this.a===window.a就是1,累加后就是2,变量a依然
//会沿着作用域链向上查找,找到上一层的是20,因为上面已经被累加了一次,
//这是一个坑,很容易忘记,上下两次调用是会互相影响的,a=20在累加就是40
//答案为:
//obj.show();//10 20
//var obj1 = obj.show;
//obj1();//2 40
第二道(闭包的经典题目,原题奉上)
function fun(n,o) {
console.log(o);
return {
fun:function(m){
return fun(m,n);
}
};
};
//写出a,b,c的运行结果
//var a = fun(0); a.fun(1); a.fun(2); a.fun(3);
//var b = fun(0).fun(1).fun(2).fun(3);
//var c = fun(0).fun(1); c.fun(2); c.fun(3);
//这里就是考查闭包的层层嵌套与多次回调,之前面试遇到的,
//当时做的时候有点紧张,不过后面自己运行后发现做的还是正确的
//var a = fun(0); a.fun(1); a.fun(2); a.fun(3);
//第一个a保存fun(0)的运行结果即return里的对象,由于o没传参数,
//打印undefined,参数n=0被保存在当前作用域中,不会被销毁,
//供当前作用域链及以下使用,这是闭包的精髓,后面回调函数传参也是如此,
//这个a.fun(1)就是调用fun(0)的结果,传参m=1,执行返回fun(m=1,n=0)
//在执行回调fun(n=1,o=0),console.log(o)就是0,后面的a.fun(2)和
//a.fun(3)也是如此,打印都是0;
//var b = fun(0).fun(1).fun(2).fun(3);
//fun(0).fun(1)这步其实就是前面说的,
//fun(0).fun(1)的返回就是return的对象,.fun(2)在调用改对象并且
//传参m=2,返回fum(m=2,n=1)在执行回调fun(n=2,o=1)
//打印console.log(o)就是1,并且返回return对象,
//在.fun(3)在调用改对象并且传参m=3,返回fum(m=3,n=2)
//在执行回调fun(n=3,o=2)打印console.log(o)就是2
//var c = fun(0).fun(1); c.fun(2); c.fun(3);
//前面两个理解了,这个也不会有问题,就不多做解释了
//var a = fun(0); a.fun(1); a.fun(2); a.fun(3);//undefined 0 0 0
//var b = fun(0).fun(1).fun(2).fun(3);//undefined 0 1 2
//var c = fun(0).fun(1); c.fun(2); c.fun(3);//undefined 0 1 1
写到这里终于结束啦,js里面还有很多奇淫技巧,每次看一篇好文或者一本书都会被新的视角冲击到,前方高能,还需继续踩坑,有什么需要交流指正的请留言呀!
也可以加微信讨论哦!
以上是脚本宝典为你收集整理的JS三部曲,变量提升,this与作用域,闭包全部内容,希望文章能够帮你解决JS三部曲,变量提升,this与作用域,闭包所遇到的问题。
本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。