资讯专栏INFORMATION COLUMN

JS三部曲,变量提升,this与作用域,闭包

dongxiawu / 2410人阅读

摘要:这篇文章总结下之前看的文章和自己在工作中遇到的坑,纯手写的,有什么写的不对的请多多提出修正哈变量提升何为变量提升里面的变量在声明之前就可以使用,因为该声明已经被提升至该作用域函数或全局的顶部直接上代码和都会变量提升优先级上面可理解为函数这时

这篇文章总结下之前看的文章和自己在工作中遇到的坑,纯手写的,有什么写的不对的请多多提出修正哈

变量提升

何为变量提升?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栏切换例子

  • 素颜
  • 断桥残雪
  • 最佳歌手
var tab = document.querySelectorAll("ul>li"); for(var a=0;a

第一种

for(var a=0;a

第二种

for(var a=0;a

第三种

for(let a=0;a
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里面还有很多奇淫技巧,每次看一篇好文或者一本书都会被新的视角冲击到,前方高能,还需继续踩坑,有什么需要交流指正的请留言呀!
也可以加微信讨论哦!

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/107027.html

相关文章

  • JS核心知识点梳理——上下文、作用闭包this(上)

    摘要:引言满满的干货,面试必系列,参考大量资料,并集合自己的理解以及相关的面试题,对核心知识点中的作用域闭包上下文进行了梳理。如果在小区这个作用域找到了张老师,我就会在张老师的辅导下学钢琴我张老师房间钢琴构成了学琴的上下文环境。 showImg(https://segmentfault.com/img/bVbo4hv?w=1800&h=1000); 引言 满满的干货,面试必bei系列,参考大...

    Andrman 评论0 收藏0
  • JS基础-作用作用闭包 Part three

    摘要:作用域执行上下文变量提前函数声明提前确定值范围一段或者一个函数都会生成一个执行上下文全局一段变量定义函数声明函数变量定义函数声明参数集合变量提前代码解析执行过程变量定义提前赋值函数声明提前代码解析函数声明函数表达式执行过程执行过程执行时才能 1.作用域 执行上下文 (变量提前、函数声明提前、确定this值、arguments) 范围:一段或者一个函数(都会生成一个执行上下文) ...

    heartFollower 评论0 收藏0
  • JS闭包this详解

    摘要:删除对匿名函数的引用,以便释放内存在匿名函数从中被返回后,它的作用域链被初始化为包含函数的活动对象和全局变量对象。闭包与变量我们要注意到,闭包只能取到任意变量的最后值,也就是我们保存的是活动对象,而不是确定值。 工作中会遇到很多 this对象 指向不明的问题,你可能不止一次用过 _self = this 的写法来传递this对象,它每每会让我们觉得困惑和抓狂,我们很可能会好奇其中到底发...

    fireflow 评论0 收藏0
  • js 变量提升闭包理解

    摘要:变量的作用域无非就是两种全局变量和局部变量。其中内部函数中可以访问外部函数的变量,是因为内部函数的作用域链中包含了外部函数的作用域也可以理解为内部函数的作用范围辐射到了外部函数的作用范围另一方面,在函数外部自然无法读取函数内的局部变量。 以前学习的时候,了解过变量提升和闭包,但是没有深入了解,网上查了资料,这里记录下,只供参考。部分内容引用: https://www.cnblogs.c...

    luoyibu 评论0 收藏0
  • 形象化模拟作用链,深入理解js作用闭包

    摘要:至此作用域链创建完毕。好了,通过深入理解作用域链,我们能跟好的理解的运行机制和闭包的原理。 前言 理解javascript中的作用域和作用域链对我们理解js这们语言。这次想深入的聊下关于js执行的内部机制,主要讨论下,作用域,作用域链,闭包的概念。为了更好的理解这些东西,我模拟了当一个函数执行时,js引擎做了哪些事情--那些我们看不见的动作。 关键词: 执行环境 作用域 作用域链 变...

    txgcwm 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<