JS篇 ------ 作用域和闭包

发布于:2022-11-09 ⋅ 阅读:(347) ⋅ 点赞:(0)

第三部分 作用域和闭包

对应知识点

1、执行上下文

变量提升

(1)JavaScript 中,函数及变量的声明都将被提升到函数的最顶部。
(2)JavaScript 中,变量可以在使用后声明,也就是变量可以先使用再声明。
(3)声明提升:函数声明和变量声明总是会被解释器悄悄地被"提升"到方法体的最顶部。

	console.log(a) // undefined
    var a = 10;

    fn('zhansan') // 'zhansan'  20
     function fn(name) {
         age = 20;
         console.log(name, age)
         var age
      }

**范围:**一段 script 或者一个函数
全局: (全局上下文) 变量声明定义、函数声明
函数: (函数上下文) 变量声明定义、函数声明、this、arguments(函数参数集合)
注意:“函数声明” 和 “函数表达式” 的区别

2、this

(1)this 要在执行时才能确认值,定义时无法确认值

 var a = {
            name: 'A',
            fn: function () {
                console.log(this.name)
            }
        }

        a.fn() // this 指向 a 这个对象
        a.fn.call({ name: 'B' }) // this 指向 {name:'B'}
        var fn1 = a.fn;
        fn1()  // this 指向 window

(2)作为构造函数执行

this 指向 new 出来的对象 f

        // 作为构造函数执行
        function Foo(name, age) {
            // this = {}
            this.age = age
            this.name = name;
            // return this
            console.log(this, '构造函数this') // this 指向 new 出来的对象 f
        }

        var f = new Foo('zhangsan', 20)

(3)作为对象属性执行

this 指向该属性所属的对象 obj

        // 作为对象属性执行
        var obj = {
            name: 'lisi',
            printName: function () {
                console.log(this.name)
                console.log(this)  // this 指向该属性所属的对象 obj
            }
        }

        obj.printName()

(4)作为普通函数执行

this 指向 window对象

        // 作为普通函数执行
        function fn() {
            console.log(this) // this 指向 window对象
        }

        fn()

(5)call() apply() bind()

**共同点:**三者都可以改变函数执行时的上下文,即改变 this 的指向,三者的第一个参数都是修改后的 this
区别:
1、传参方式不同
—> call 第一个参数是对象,没有则指向 window ,从第二个参数起就是逐一传参(参数列表),这些参数与函数参数是对应的。
函数名.call(修改的 this ,参数1,参数2…)
—> apply 只接受两个参数,第一个参数是对象,没有则指向 window , 第二个参数是数组或者类数组(集合),数组或类数组的
元素对应函数参数。函数名.apply(修改的 this ,数组或者类数组)
—>bind 第一个参数是对象,没有则指向 window,从第二个参数起就是逐一传参(参数列表),这个参数可以分多次传入。
函数名.bind (修改的 this ,参数1,参数2…)
2、执行机制不同
—> 改变 this 指向后,call 和 apply 会立即调用,bind不会立即调用,而是返回一个永久改变 this 指向的函数

call()

使用举例

        // call
        function thisCall(...args) {
            console.log(this, args)
        }

        var dog = {
            name: 'laifu'
        }

        // this 指向改变为 dog 对象,传入一个参数列表
        thisCall.call(dog, 2, '香肠');

        thisCall(3, 4)  // this 指向 window

实现call
考虑 call 的功能,存在两个关键点
—> 不传入第一个参数,默认为 window
—> 改变了 this 指向,让新的对象(改变后 this 指向的对象)可以执行该函数,
—> **思路:**给新对象添加一个函数,执行完之后删除

        // 实现 call 
        Function.prototype.myCall = function (context){
            var context = context || window 
            // 给 context 添加一个属性,
            // getValue.call(a,'yck','24') => a.fn = getValue
            context.fn = this
            // 将 context 后面的参数取出来
            var args = [...arguments].slice(1)
            // getValue.call(a,'yck','24') => a.fn('yck','24')
            var result = context.fn(...args)
            // 删除 fn
            delete context.fn
            return result
        }
apply()

使用举例

        // apply  只接受两个参数  第一个参数为指向对象,第二个参数为数组或者类数组
        function thisApply(...args) {
            console.log(this, args);
        }

        var person = {
            name: 'zhangsan'
        }

        // this 指向改变为 person 对象 , 传入的第二个参数必须是一个数组或者类数组
        thisApply.apply(person, [1, 2]);

        thisApply(1, 2); // this 指向 window

实现apply

        //  实现 apply
        Function.prototype.myApply = function (context) {
            var context = context || window
            context.fn = this

            var result
            // 需要判断是否存储第二个参数
            // 如果存在 就将第二个参数展开

            if (arguments[1]) {
                result = context.fn(...arguments[1])

            } else {
                result = context.fn()
            }
            delete context.fn
            return result
        }
bind()

使用举例

        // bind 

        function thisBind(age) {
            console.log(this, age, 89898); // ƒ thisBind(age) { console.log(this, age, 89898); }
        }

        var cat = {
            name: 'mimi'
        }

        // 改变了 thisBind 中的this,this 指向改变为 cat 对象,thisBind 并不立即执行,
        // 并且返回了返回一个永久改变 this 指向的函数
        thisBind.bind(cat, 3)
        thisBind(6,7)

实现bind
需要考虑以下三点
—> bind 返回一个函数
—> 参数可以多次传入
—> 当 bind 返回的函数作为构造函数的时候,bind 执行时,指定的 this 值会失效,但传入的参数依然生效

        // 实现 bind
        Function.prototype.myBind = function (context) {
            if (typeof this !== 'function') {
                throw new TypeError('Error')
            }
            var _this = this
            var args = [...arguments].slice(1)
            // 返回一个函数
            return function F() {
                // 因为返回一个函数,我们可以 new F(), 所以需要判断
                if (this instanceof F) {
                    return new _this(...args, ...arguments)
                }
                return _this.apply(context,args.concat(...arguments))
            }

        }

3、作用域

(1)没有块级作用域

        // (1)没有块级作用域
        if (true) {
            var name = 'zhangsan' // 在这里声明定义
        }
        console.log(name) // 在 if 块外面也可以获取 即没块级作用域 
        // 良好的代码习惯 在哪里声明定义就在哪里用 不推荐这样使用

(2)只有函数(局部)作用域和全局作用域

        // (2) 函数(局部)作用域和全局作用域
        var a = 100
        function fn() {
            var a = 200
            console.log('fn', a) // 函数(局部)作用域
        }
        console.log('global', a) // 全局作用域
        fn()

4、作用域链

**解释:**某个作用域中的“自由变量”向父级作用域寻找自身声明定义的过程就形成作用域链。
(1)当前作用域没有定义的变量,即 “自由变量”。
(2)变量或函数在哪里声明定义就往哪里去找其父级作用域。

        // 4、作用域链
        var c = 300
        function fn1(){
            var b = 500
            // 当前作用域没有定义的变量,即 “自由变量”
            function fn2(){
            var c = 700
            console.log(a) // a在全局作用域定义的 即a在该函数中是自由变量
            console.log(b) // b 在fn2 的父级 fn1 函数作用域定义 b 也是自由变量
            console.log(c)
            }
            fn2()

        }

5、闭包

解释:闭包指的是那些引用了另外一个函数作用域中变量的函数, 通常是在嵌套函数中实现的。

(1)函数作为返回值

        // 函数作为返回值
        function F1() {
            var a = 100
            // 返回一个函数 (函数作为返回值)
            return function () {
                console.log(a) // 100
            }
        }
        // f1 得到一个函数  赋值给f1
        var f1 = F1()
        var a = 200
        f1()

(2)函数作为参数传递

        //  函数作为参数来传递
        function F1() {
            var a = 100
            // 返回一个函数 (函数作为返回值)
            return function () {
                console.log(a) // 100
            }
        }
        // f1 得到一个函数  赋值给f1
        var f1 = F1()

        function F2(fn) {
            var a = 200
            fn()
        }

        F2(f1) // a 100 函数作为参数来传递

对应面试题

1、说一下对变量提升的理解

(对应知识点------执行上下文)
围绕以下方面作答理解
(1)变量定义
(2)函数声明(注意函数声明和函数表达式的区别)
变量提升
JavaScript 中,函数及变量的声明都将被提升到函数的最顶部。
JavaScript 中,变量可以在使用后声明,也就是变量可以先使用再声明。
声明提升:函数声明和变量声明总是会被解释器悄悄地被"提升"到方法体的最顶部。

2、说明 this 几种不同的使用场景

(1)this 要在执行时才能确认值,定义时无法确认值
(2)作为构造函数执行
(3)作为对象属性执行
(4)作为普通函数执行
(5)call apply bind

3、创建10个 a 标签,点击的时候弹出来对应的序号

作用域问题
实现效果

        var i
        for (i = 0; i < 10; i++) {
            (
                function (i) {
                    var a = document.createElement('a')
                    a.innerHTML = i + '<br>'
                    a.addEventListener('click', function (e) {
                        e.preventDefault() // 取消事件的默认动作
                        alert(i)
                    })
                    document.body.appendChild(a) // appendChild() 方法可向节点的子节点列表的末尾添加新的子节点
                }
            )(i)
        }

4、如何理解作用域

(1)没有块级作用域
(2)只有函数(局部)作用域和全局作用域
(3)自由变量
(4)作用域链,即自由变量的查找
(5)闭包的两个场景
—> 函数作为返回值
—> 函数作为参数传递

5、实际开发中闭包的应用

闭包实际应用中主要用于封装变量,收敛权限

        // 实际中闭包的应用
        // 闭包实际应用中主要用于封装变量,收敛权限
        function isFirstLoad() { 
            var _list = []
            return function (id) {
                if (_list, indexOf(id) >= 0) {
                    return false
                } else {
                    _list.push(id)
                    return true
                }
            }
        }
        // 使用
        var firstLoad = isFirstLoad()
        firstLoad(10) // true
        firstLoad(10) // false
        firstLoad(20) // true
本文含有隐藏内容,请 开通VIP 后查看