浏览器工作原理与实践
李兵
前盛大创新院高级研究员
立即订阅
6167 人已学习
课程目录
已完结 42 讲
0/4登录后,你可以任选4讲全文学习。
开篇词 (1讲)
开篇词 | 参透了浏览器的工作原理,你就能解决80%的前端难题
免费
宏观视角下的浏览器 (6讲)
01 | Chrome架构:仅仅打开了1个页面,为什么有4个进程?
02 | TCP协议:如何保证页面文件能被完整送达浏览器?
03 | HTTP请求流程:为什么很多站点第二次打开速度会很快?
04 | 导航流程:从输入URL到页面展示,这中间发生了什么?
05 | 渲染流程(上):HTML、CSS和JavaScript,是如何变成页面的?
06 | 渲染流程(下):HTML、CSS和JavaScript,是如何变成页面的?
浏览器中的JavaScript执行机制 (5讲)
07 | 变量提升:JavaScript代码是按顺序执行的吗?
08 | 调用栈:为什么JavaScript代码会出现栈溢出?
09 | 块级作用域:var缺陷以及为什么要引入let和const?
10 | 作用域链和闭包 :代码中出现相同的变量,JavaScript引擎是如何选择的?
11 | this:从JavaScript执行上下文的视角讲清楚this
V8工作原理 (3讲)
12 | 栈空间和堆空间:数据是如何存储的?
13 | 垃圾回收:垃圾数据是如何自动回收的?
14 | 编译器和解释器:V8是如何执行一段JavaScript代码的?
浏览器中的页面循环系统 (6讲)
15 | 消息队列和事件循环:页面是怎么“活”起来的?
16 | WebAPI:setTimeout是如何实现的?
17 | WebAPI:XMLHttpRequest是怎么实现的?
18 | 宏任务和微任务:不是所有任务都是一个待遇
19 | Promise:使用Promise,告别回调函数
20 | async/await:使用同步的方式去写异步代码
浏览器中的页面 (8讲)
21 | Chrome开发者工具:利用网络面板做性能分析
22 | DOM树:JavaScript是如何影响DOM树构建的?
23 | 渲染流水线:CSS如何影响首次加载时的白屏时间?
24 | 分层和合成机制:为什么CSS动画比JavaScript高效?
25 | 页面性能:如何系统地优化页面?
26 | 虚拟DOM:虚拟DOM和实际的DOM有何不同?
27 | 渐进式网页应用(PWA):它究竟解决了Web应用的哪些问题?
28 | WebComponent:像搭积木一样构建Web应用
浏览器中的网络 (3讲)
29 | HTTP/1:HTTP性能优化
30|HTTP/2:如何提升网络速度?
31|HTTP/3:甩掉TCP、TLS 的包袱,构建高效网络
浏览器安全 (5讲)
32 | 同源策略:为什么XMLHttpRequest不能跨域请求资源?
33 | 跨站脚本攻击(XSS):为什么Cookie中有HttpOnly属性?
34 | CSRF攻击:陌生链接不要随便点
35 | 安全沙箱:页面和系统之间的隔离墙
36 | HTTPS:让数据传输更安全
结束语 (1讲)
结束语 | 大道至简
课外加餐 (4讲)
加餐一|浏览上下文组:如何计算Chrome中渲染进程的个数?
加餐二|任务调度:有了setTimeOut,为什么还要使用rAF?
加餐三|加载阶段性能:使用Audits来优化Web性能
加餐四|页面性能工具:如何使用Performance?
浏览器工作原理与实践
登录|注册

11 | this:从JavaScript执行上下文的视角讲清楚this

李兵 2019-08-29
上篇文章中,我们讲了词法作用域、作用域链以及闭包,并在最后思考题中留了下面这样一段代码:
var bar = {
myName:"time.geekbang.com",
printName: function () {
console.log(myName)
}
}
function foo() {
let myName = "极客时间"
return bar.printName
}
let myName = "极客邦"
let _printName = foo()
_printName()
bar.printName()
相信你已经知道了,在 printName 函数里面使用的变量 myName 是属于全局作用域下面的,所以最终打印出来的值都是“极客邦”。这是因为 JavaScript 语言的作用域链是由词法作用域决定的,而词法作用域是由代码结构来确定的。
不过按照常理来说,调用bar.printName方法时,该方法内部的变量 myName 应该使用 bar 对象中的,因为它们是一个整体,大多数面向对象语言都是这样设计的,比如我用 C++ 改写了上面那段代码,如下所示:
#include <iostream>
using namespace std;
class Bar{
public:
char* myName;
Bar(){
myName = "time.geekbang.com";
}
void printName(){
cout<< myName <<endl;
}
} bar;
char* myName = "极客邦";
int main() {
bar.printName();
return 0;
}
在这段 C++ 代码中,我同样调用了 bar 对象中的 printName 方法,最后打印出来的值就是 bar 对象的内部变量 myName 值——“time.geekbang.com”,而并不是最外面定义变量 myName 的值——“极客邦”,所以在对象内部的方法中使用对象内部的属性是一个非常普遍的需求。但是 JavaScript 的作用域机制并不支持这一点,基于这个需求,JavaScript 又搞出来另外一套 this 机制
所以,在 JavaScript 中可以使用 this 实现在 printName 函数中访问到 bar 对象的 myName 属性了。具体该怎么操作呢?你可以调整 printName 的代码,如下所示:
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《浏览器工作原理与实践》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(46)

  • ytd
    // 修改方法一:箭头函数最方便
    let userInfo = {
      name:"jack.ma",
      age:13,
      sex:'male',
      updateInfo:function(){
        // 模拟 xmlhttprequest 请求延时
        setTimeout(() => {
          this.name = "pony.ma"
          this.age = 39
          this.sex = 'female'
        },100)
      }
    }

    userInfo.updateInfo()
    setTimeout(() => {
      console.log(userInfo)
    },200)

    // 修改方法二:缓存外部的this
    let userInfo = {
      name:"jack.ma",
      age:13,
      sex:'male',
      updateInfo:function(){
        let me = this;
        // 模拟 xmlhttprequest 请求延时
        setTimeout(function() {
          me.name = "pony.ma"
          me.age = 39
          me.sex = 'female'
        },100)
      }
    }

    userInfo.updateInfo()
    setTimeout(() => {
      console.log(userInfo);
    },200)

    // 修改方法三,其实和方法二的思路是相同的
    let userInfo = {
      name:"jack.ma",
      age:13,
      sex:'male',
      updateInfo:function(){
        // 模拟 xmlhttprequest 请求延时
        void function(me) {
          setTimeout(function() {
            me.name = "pony.ma"
            me.age = 39
            me.sex = 'female'
          },100)
        }(this);
      }
    }

    userInfo.updateInfo()
    setTimeout(() => {
      console.log(userInfo)
    },200)

    let update = function() {
      this.name = "pony.ma"
      this.age = 39
      this.sex = 'female'
    }

    方法四: 利用call或apply修改函数被调用时的this值(不知掉这么描述正不正确)
    let userInfo = {
      name:"jack.ma",
      age:13,
      sex:'male',
      updateInfo:function(){
        // 模拟 xmlhttprequest 请求延时
        setTimeout(function() {
          update.call(userInfo);
          // update.apply(userInfo)
        }, 100)
      }
    }

    userInfo.updateInfo()
    setTimeout(() => {
      console.log(userInfo)
    },200)

    // 方法五: 利用bind返回一个新函数,新函数被调用时的this指定为userInfo
    let userInfo = {
      name:"jack.ma",
      age:13,
      sex:'male',
      update: function() {
        this.name = "pony.ma"
        this.age = 39
        this.sex = 'female'
      },
      updateInfo:function(){
        // 模拟 xmlhttprequest 请求延时
        setTimeout(this.update.bind(this), 100)
      }
    }

    作者回复: 很赞,总结的很全,这个可以做参考答案

    2019-08-29
    1
    19
  • William
    setTimeOut() 函数内部的回调函数,this指向全局函数。修复:在外部绑this或者使用箭头函数。
    ```
    let userInfo = {
      name:"jack.ma",
      age:13,
      sex: "male",
      updateInfo:function(){
        let that = this;
        // 模拟 xmlhttprequest 请求延时
        setTimeout(()=>{
          that.name = "pony.ma"
          that.age = 39
          that.sex = "female"
        },100)
      }
    }

    userInfo.updateInfo()
    ```

    ```
    let userInfo = {
      name:"jack.ma",
      age:13,
      sex: "male",
      updateInfo:function(){
        // 模拟 xmlhttprequest 请求延时
        setTimeout(()=>{
          this.name = "pony.ma"
          this.age = 39
          this.sex = "female"
        },100)
      }
    }

    userInfo.updateInfo()
    ```

    作者回复: 非常好!

    补充下解释:
    如果被setTimeout推迟执行的回调函数是某个对象的方法,那么该方法中的this关键字将指向全局环境,而不是定义时所在的那个对象。

    如果是严格模式,那么this会被设置为undefined。

    这一点很容易让人混淆!!!

    2019-08-29
    1
    13
  • 悬炫
    关于箭头函数,文章中说其没有自己的执行上下文,难道箭头函数就像let定义的变量一样是哥块级作用域吗?其内部定义的变量都是存储在词法环境中是吗?

    作者回复: 箭头函数在执行时比块级作用域的内容多,比函数执行上下文的内容少,砍掉了很多函数执行上下文中的组件。

    不过在箭头函数在执行时也是有变量环境的,因为还要支持变量提升!所以变量环境的模块还是砍不掉的

    2019-08-29
    1
    8
  • 风一样的浪漫
    老师请问下outer的位置是在变量对象内还是外,第10节描述是在内部的,可是11节的图outer放在变量对象外面了

    作者回复: 是在里面的,11为了图简单点,调整到外面了

    2019-09-05
    1
    4
  • pyhhou
    思考题,有两种方法
    1. 将 setTimeout 里面的函数变成箭头函数
    2. 在 setTimeout 外将 this 赋值给其他的变量,setTimeout 里面的函数通过作用域链去改变 userInfo 的属性

    很不错的文章,受益匪浅,感谢老师。这里有一个疑问就是,关于箭头函数,文章中说其没有自己的执行上下文,这里指的是箭头函数并不会创建自己的执行上下文变量并压栈,其只是被看作是一个块区域吗?那么在实际的开发中如何在普通函数和箭头函数之间做选择?关于这一点,老师有没有相关推荐的文章呢?谢谢老师

    作者回复: 箭头要展开了得话一节来讲,关于箭头函数的最佳实践网上应该有不少资料,可以查查!

    2019-08-29
    3
  • 李懂
    文章只是简单讲了下this的几种场景,不像前面变量申明,可以很清晰的知道在执行上下文的位置,也没有画图,看完还是不知道不能深入理解,更多的是一种记忆,这种是指向window,那种是指向对象。能不能深入到是如何实现this,才能知道缺陷的原因,这里一直是没理解的难点!

    作者回复: this的缺陷并不是浏览器实现机制导致的,而是浏览器按照标准来实现的。

    其实浏览器说我可以实现得更好,但是标准摆在这儿,大家都只认标准!

    2019-08-29
    2
    2
  • 大威先生🐯🐯
    最后一个案例中,myObj对象的 showThis函数内部定义了bar函数,bar函数的执行环境具有全局性,因此this对象通常指向window;----摘要《JavaScript高级程序设计》
    2019-10-27
    1
  • Heartbeats
    谁调用了它,它就指向谁
    2019-09-10
    2
    1
  • 爱吃锅巴的沐泡
    对于思考题的一些问题:
    1、对象中定义的方法是一个全局函数嘛?
    2、setTimeout()的回调函数中的this指向window 是因为widow调用setTimeout()? 还是因为 定义的回调函数和 外部的updateInfo函数嵌套定义?或者还是因为其他的原因?
    3、这里的setTimeout()的回调函数属于是一个字面量的函数定义作为参数进行传递,这种参数形式的函数定义与外面的updateInfo()函数可以算作嵌套定义嘛?(这里好像又回到了问题2)
    2019-09-02
    1
  • 朱维娜🍍
    之前看到一种说法:this指向的永远是调用它的对象。按照这种说法,嵌套函数的调用者是window,与文中所述的“showThis调用内部函数不能继承this”有所出入,想请老师解答一下这种说法是否正确?

    作者回复: 调用者是对象,函数内部是调用的地方,不能说是调用者。
    obj.showThis()
     这里的obj是调用者,通过点操作符来实现的

    2019-08-31
    2
    1
  • Winn
    通俗易懂,由简入深,把this说得最清楚的文章
    2019-08-30
    1
  • 七月有风
    做题面试写代码,这些就够了,但this到底是什么,还是不懂
    2019-08-29
    1
  • 潘启宝
    let userInfo = {
      name:"jack.ma",
      age:13,
      sex:'male',
      updateInfo:function(){
        // 模拟 xmlhttprequest 请求延时
        setTimeout(function(){
          this.name = "pony.ma"
          this.age = 39
          this.sex = 'female'
        }.bind(this),100)
      }
    }

    userInfo.updateInfo()

    作者回复: 使用bind没有问题

    2019-08-29
    4
    1
  • mfist
    延时函数更新此时的this对象指向了window全局对象。
    解决方法就是文章老师提到的两种方法。
    1 this保存给self变量,通过变量作用域机制传递给嵌套函数。
    2箭头函数去锁定函数定义时候的this对象,箭头函数没有上下文,它会继承函数初始化对应上下文。

    思考:
    1 能否通过bind和apply改变箭头函数this指向?
    回头试一下,然后好好理理这几节内容

    作者回复: 建议看看其他老铁的留言

    2019-08-29
    1
  • Geek_East
    一般老说,人们更习惯于lexically寻找标识符;
    解决this动态性带来的不稳定问题,可以通过将this机制转化为词法作用域的方式来实现。
    无论是 self=this 还是arrow function,都是通过这种方式来实现;如果使用new,bind, apply的方式其实会定死,反而倒是觉得没有词法作用域链这么灵活;
    2019-12-08
  • Geek_East
    “ES6 中的箭头函数并不会创建其自身的执行上下文,所以箭头函数中的 this 取决于它的外部函数”, 这句话中,它的“外部函数”应该如何理解?是指lexical scope的外部,还是指执行上下文的外部?
    2019-12-08
  • vianem
    上一节说道全局执行上下文的outer为null,但是这一节似乎表达了全局执行上下文的outer指向window对象?
    2019-11-27
  • senekis
    老师的这几篇文章太精彩了,解答了我学习js中很多疑问~!非常感谢老师!
    2019-10-31
  • 凭实力写bug
    我记得执行上下文包括变量环境,词法环境,outer,this,如果箭头函数没有执行上下文,他的这些内容又是怎样的,还有他的作用域呢
    2019-10-31
  • 给我一片海
    let userInfo = {
      name:"jack.ma",
      age:13,
      sex:'male',
      updateInfo:function(){
        setTimeout(function(){
          this.name = "pony.ma"
          this.age = 39
          this.sex = 'female'
        }.bind(this),100)
      }
    }

    userInfo.updateInfo()
    2019-10-21
收起评论
46
返回
顶部