极客时间浏览器工作原理与实践读书笔记 < 二 >(作用域链、闭包、this)

作用域链

function bar() {
    console.log(myName)
}
function foo() {
    var myName = "阿春"
    function bar2() {
        console.log('2', myName);
    }
    bar();
    bar2();
}
var myName = "阿春呀"
foo()
// 阿春呀
// 2 阿春

变量值的引用不是按照调用栈的顺序来查找变量。每个执行上下文的变量环境中都包含了一个外部引用outer,用来指向外部的执行上下文。上面的代码段中,bar和foo指向的都是全局上下文。这个查找的链条就是作用域链

  • 词法作用域:指作用域是由代码中函数声明的位置来决定的,是代码编译阶段就决定好的,和函数如何调用无关。

比如上面的代码块中,存在的词法作用域链为:bar() -> 全局作用域 | bar2() -> foo() -> 全局作用域

function bar() {
    var myName = "bar"
    let test1 = 100
    if (1) {
        let myName = "{}内}"
        console.log(test)
    }
}
function foo() {
    var myName = "foo"
    let test = 2
    {
        let test = 3
        bar()
    }
}
var myName = "window"
let myAge = 20
let test = 1
foo()

首先查找bar内的词法环境 myName/test1;接着查找函数变量环境 myName;接着随着outer查找全局执行上下文,首先看词法环境myAge,test。找到值,因此输出1。

闭包

  • 闭包:在 JavaScript 中,根据词法作用域的规则,内部函数总是可以访问其外部函数中声明的变量,当通过调用一个外部函数返回一个内部函数后,即使该外部函数已经执行结束了,但是内部函数引用外部函数的变量依然保存在内存中,我们就把这些变量的集合称为闭包。比如外部函数是 foo,那么这些变量的集合就称为 foo 函数的闭包。
function foo() {
    var myName = "极客时间"
    let test1 = 1
    const test2 = 2
    var innerBar = {
        getName:function(){
            console.log(test1)
            return myName
        },
        setName:function(newName){
            myName = newName
        }
    }
    return innerBar
}
var bar = foo()
bar.setName("极客邦") // JavaScript 引擎会沿着“当前执行上下文–>foo 函数闭包–> 全局执行上下文”的顺序来查找 myName 变量,
bar.getName()
console.log(bar.getName())

根据词法作用域,内部函数getName和setName总是可以访问它们的外部函数foo中的变量,所以当innerBar对象返回给全局变量bar时,虽然foo函数已经执行结束,但其中的test1和myName依然保存在内存中,这些变量除了bar,其他地方都无法访问,这个变量集Closure(foo)称为闭包。

  • 闭包的回收:如果闭包一直使用,那么可以作为一个全局变量存在;如果使用频率不高,尽量成为一个局部变量。因为全局变量闭包会一直存在直到页面关闭,但如果这个闭包不再使用,会造成内存泄漏。

  • 闭包的应用

// 闭包实现计数器
var add = (function(){
      var counter = 0;
      return function(){
             return(++counter);
      }
})();
  • 极客时间课后题:
var bar = {
    myName:"time.geekbang.com",
    printName: function () {
        console.log(this.myName)
    }    
}
function foo() {
    let myName = "极客时间"
    return bar.printName
}
let myName = "极客邦"
let _printName = foo()
_printName()
bar.printName()

不会形成闭包,输出为 极客邦 极客邦。因为JavaScript语言的作用域链是由词法作用域决定的,而词法作用域是由代码结构决定的。因此printName中使用的变量是全局作用域下的。

// 引入this
var bar = {
    myName1:"time.geekbang.com",
    printName: function () {
        console.log(this.myName1)
    }    
}
function foo() {
    let myName = "极客时间"
    return bar.printName
}
let myName = "极客邦"
let _printName = foo()
_printName()// this指向window,输出极客邦
bar.printName()// this指向bar,

会形成闭包,_printName(): this指向window,输出极客邦;bar.printName(): this指向bar,time.geekbang.com。

this

因为JavaScript中也需要在对象内部的方法中使用对象内部的属性,因此存在this机制。

执行上下文

  • 全局执行上下文:执行全局代码的时候编译并创建全局执行上下文,整个页面的生存周期内,只有一份;
  • 函数执行上下文:调用函数的时候编译并创建,函数执行结束后,创建的函数执行上下文会被销毁;
  • eval函数也会创建执行上下文。

this的指向

  • 使用create创建对象时,this指向该对象(通过构造函数中设置)
function CreateObj(){
  console.log(this);
  this.name = "是阿春呀";
}
CreateObj(); // window
var myObj = new CreateObj(); // CreateObj

new操作时,首先创建一个空对象tempObj(var tempObj = {});接着调用CreateObj.call(tempObj)方法(CreateObj.call(tempObj));然后执行CreateObj函数(此时CreateObj的this指向tempObj对象);最后返回tempObj对象。(return tempObj)

  • this的值不由函数定义放在哪个对象里决定,而是函数执行时由谁来唤起决定。(通过对象调用方法设置)
var person = {
    name: "Jay",
    greet: function() {
        console.log("hello, " + this.name);
    }
};
person.greet();
// hello, Jay
// person调用,this指向person

var greet = person.greet;
greet();
// hello,
// window调用,此时是undefined
  • 特殊的箭头函数:箭头函数按词法作用域来绑定它的上下文,所以this实际上会引用到原来的上下文。

箭头函数保持它当前执行上下文的词法作用域不变,而普通函数则不会。换句话说,箭头函数从包含它的词法作用域中继承到了 this 的值。

var object = {
    data: [1,2,3],
    dataDouble: [1,2,3],
    double: function() {
        console.log("this inside of outerFn double()");
        console.log(this);// 1
        return this.data.map(function(item) {
            console.log(this);// 2
            return item * 2;
        });
    },
    doubleArrow: function() {
        console.log("this inside of outerFn doubleArrow()");
        console.log(this);//3
        return this.dataDouble.map(item => {
            console.log(this);//4
            return item * 2;
        });
    }
};
object.double(); // 1.object对象2.window对象(因为map是window下的函数)
object.doubleArrow(); // 3.object对象4.object对象

  • 严格模式 use strict: 在严格模式下,最外层的this不指向window而是undefined

this指向练习题

var myObj = {
  name : "是阿春呀", 
  showThis: function(){
    var self = this;
    console.log(this)// myObj
    function bar(){
      console.log(this); // window(因为this的绑定可以通过new运算符、call/apply/bind、通过对象调用等方式进行,但是不会通过作用域链绑定this,所以对于独立调用的函数,如果未进行有效的this绑定的话,this就会绑定到window对象或undefined)
      console.log(self); // myObj(将this体系转换为了作用域体系)
    }
    bar()
  }
}
var myObj = {
  name : "是阿春呀", 
  showThis: function(){
    console.log(this) // myObj
    var bar = () => {console.log(this)} // myObj
    bar()
  }
}
myObj.showThis()
var x = 11;
var obb1 = {
  x: 222,
  y: {
    x: 333,
    obc: function f() {
      console.log('a', this);
      var x = 111;
      var obj = {
        x: 22,
        say: () => {
          console.log('b', this.x);
        }
      }
      obj.say()
    }
  }
}
var obb2 = {
  x: 222,
  y: {
    x: 333,
    obc: function f() {
      console.log('a', this);
      var x = 111;
      var obj = {
        x: 22,
        say: function f() {
          console.log('b', this.x);
        }
      }
      obj.say()
    }
  }
}
obb1.y.obc();
// a {x: 333, obc: ƒ} b 333
obb2.y.obc();
// a {x: 333, obc: ƒ} b 22

解析:=>继承之前的this作用域,不会新增作用域

function fn1()  
{  
    this.user = '是阿春呀';  
    return {};  
}
var a = new fn1;  
function fn2()  
{  
    this.user = '是阿春呀';  
    return 0;// 或者null,undefined
}
var b = new fn2;  

console.log(a.user); //undefined
console.log(b.user); //是阿春呀

如果返回值是一个对象,那么this指向的就是那个返回的对象,如果返回值不是一个对象那么this还是指向函数的实例。虽然null也是对象,但是在这里this还是指向那个函数的实例,因为null比较特殊

修改this指向

  • bind: func.bind(this, param1, param2, …)

适用:参数固定,不立即执行

var bobObj = {
    name: "Bob"
};
function print() {
    return this.name;
}
// 将 this 明确指向 "bobObj"
var printNameBob = print.bind(bobObj);
console.log(printNameBob());    // this 会指向 bob,于是输出结果是 "Bob"
  • call: print.call(this, param1, param2, …),并执行函数

适用:参数固定,立即执行,

var item = {
    name: "I am"
};
function print() {
    return this.name;
}
// 立刻执行
var printNameBob = console.log(print.call(item));

// 将 argument 对象转化成一个数组
function add (a, b) { 
    return a + b; 
}
function sum() {
    return Array.prototype.reduce.call(arguments, add);
}
console.log(sum(1,2,3,4)); // 10
  • apply: func.apply(this, [params…])
Math.min.apply(null, [1,2,3,4]);
// 返回 1
console.log.apply(console, [1,3,'I am a string', {name: "jay", age: "1337"}, [4,5,6,7]]);
// 1 3 "I am a string" {name: "jay", age: "1337"} (4) [4, 5, 6, 7]

后续待学习

  • 方法借用
  • 柯里化
  • 偏函数应用
  • 依赖注入

参考资料

  • 《深入理解JavaScript this》作者:老教授 链接:https://juejin.im/post/5aefe76e6fb9a07abc29d4a1 来源:掘金
  • 《浏览器工作原理与实践》作者:李兵
    https://time.geekbang.org/column/intro/100033601?tab=catalog 来源:极客时间
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值