84

前端面试之js相关问题(一)

 6 years ago
source link: https://juejin.im/post/5a5461ec6fb9a01cba4271bc
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

最近我也是经历过面试别人和去面试的人了,总结几个常被提及的面试问题,做一下解答和备忘。

JavaScript 中 this 是如何工作的 ?

先来看看这个题目:

var x = 0;
var foo = {
    x:1,
    bar:{
    x:2,
    baz: function () {
       console.log(this.x)
     }
    }
}

var a = foo.bar.baz
foo.bar.baz() // 2
a() //0
复制代码
  • this 永远指向函数运行时所在的对象,而不是函数创建时所在的对象
  • 匿名函数和不处于任何对象中的函数,This指向window
  • call, apply, with指的This是谁就是谁。
  • 普通函数调用,函数被谁调用,This就指向谁

上面的例子中,baz被bar调用所以指向的指bar. a 运行时所在的对象是 window,所以指向的是window。

作用域链?

理解执行环境和上下文

函数调用都有与之相关的作用域和上下文。从根本上说,作用域是基于函数(function-based)而上下文是基于对象(object-based)。换句话说,作用域是和每次函数调用时变量的访问有关,并且每次调用都是独立的。上下文总是关键字 this 的值,是调用当前可执行代码的对象的引用。

执行上下文分有globalfunctioneval,一个函数可以产生无数个执行上下文,一系列的执行上下文从逻辑上形成了 执行上下文栈,栈底总是全局上下文,栈顶是当前(活动的)执行上下文。

执行上下文三属性:this指针,变量对象(数据作用域),作用域链

作用域链 即:一变量在自己的作用域中没有,那么它会寻找父级的,直到最顶层。过程如下:

  • 任何在执行上下文时刻的作用域都由作用域链来实现
  • 在一个函数被定义的时候, 会将它定义时刻的scope chain链接到这个函数对象的[[scope]]属性
  • 在一个函数对象被调用的时候,会创建一个活动对象(也就是一个对象), 然后对于每一个函数的形参,都命名为该活动对象的命名属性, 然后将这个活动对象做为此时的作用域链(scope chain)最前端, 并将这个函数对象的[[scope]]加入到scope chain中.

上面的文字大家可以好好琢磨一下,可以更好的理解函数作用域。

函数声明提升和变量声明提升(Hoisting) ?

我们先来了解js编译器在执行代码的过程:
以执行一段function代码为例:
第一步:创建可执行上下文(以下简称为EC),压入当前的EC栈中。EC中包括了以下信息:

  • 词法环境(=环境记录项(保存变量、函数声明和形参)+ 外部词法环境(function的[[scope]]属性,作用域链的本质))
  • this的指针
  • 变量环境(与环境记录项的值相同,但不再发生变动。)

第二步:收集函数声明变量声明形参,保存在环境记录项内。这个收集的过程,就是一般所谓的声明提升现象的本质。如果发现了重复的标识符,则优先级函数声明形参变量声明(优先级低的会被无视)。

第三步:开始执行代码,环境记录项内没有的标识符会根据作用域链查找标识符对应的值,环境记录项亦有可能因赋值语句而被修改。

第四步:函数执行完毕,EC栈被弹出、销毁。

好了,第二步说的很清楚了 声明提升(Hoisting)现象就是在收集函数、变量声明和形参的过程会根据函数声明、形参、变量声明的顺序优先级来收集。

var a = 1;  
function b() {  
    a = 10;  
    return;  
    function a() {}  
}  
b();  
console.log(a); 
// 输出1 由于函数声明提升,b内的实际是这样:
// function b() {  
//    function a() {}; 这里是函数声明提升
//    a = 10;  
//    return;  
//    function a() {}  
// }
复制代码

理解了吗?

勘误:谢谢github上有同学的指正关于博客中的一个问题 · Issue #1 · stephenzhao/hexo-theme-damon,上面的正确执行应该为先进行预编译,所以先执行function a(){},然后会进行对a的赋值操作。
//正确的顺序应该为:
// function b() {  
//    function a() {}  
//    a = 10;  
//    return;  
// }
复制代码

什么是闭包,如何使用它,为什么要使用它?

还是上面的题目,做个变形。

var x = 0;
var foo = {
    x:1,
    bar:function () {
        console.log(this.x);
        var that = this;
        return function () {
           console.log(this.x)
           console.log(that.x)
        }
    }
}


foo.bar()       // 1
foo.bar()()     // this: 0, that: 1
复制代码

上面的例子中ba'r里面返回了一个匿名函数,这个匿名函数可以在外部被调用即:foo.bar()() 读取到了bar的执行上下文的变量对象 that,这个函数就形成了一个闭包。

好了,我们理解了上面的套路,下面来解释闭包就好理解了。

闭包就是能够读取其它函数内部变量的函数

在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成“定义在一个函数内部的函数”

var x = 0;
var bar:function () {
        var n = 999;
        return function () {
           return n;
        }
    }
var outer = bar();
outer() // 999
复制代码
  1. 读取函数内部的变量
  2. 让这些变量的值始终保持在内存中

我们修改一下上面的代码

var add;
var bar = function () {
        var n = 999;
        add = function () {
            n += 1;
        }
        return function () {
           return n;
        }
    }
var outer = bar();
outer() // 999 
add();
outer(); // 1000
复制代码

说明,n一直保存在内存当中,而没有在bar()执行完成之后被销毁;
原因:
bar里面的匿名函数被赋值给了outer,这个导致在outer没有被销毁的时候,该匿名函数一直存在内存当中,而匿名函数的存在依赖于bar,所以bar需要使用都在内存当中,所以bar并不会在调用结束后呗垃圾回收机制给收回。

而上面的add接受的也是一个匿名函数,该匿名函数本身也是闭包,所以也可以在外部操作里面的变量。

  1. 会导致内存泄漏,慎用
  2. 闭包会修改内部变量的值,所以在使用闭包作为对象的公用方法时要谨慎。
    闭包的一个应用,单例模式

单例模式的定义是产生一个类的唯一实例

单例模式在js中经常会遇到,比如 var a = {}; 其实就是一个单例子。

但是我们写一个更有意义的单例:

var singleton = function( fn ){
    var result;
    return function(){
        return result || ( result = fn .apply( this, arguments ) );
    }
}
复制代码

更简洁一点的:

var singleton = (function () {
    var instance;
    return function (object) {
        if(!instance){
            instance = new object();
        }
        return instance;
    }
    })();
复制代码

又是半夜,这两天在看里约奥运会的比赛,林丹和李宗伟的那场比赛是今年看过的经次于nba总决赛最后一场的精彩程度。一个伟大的英雄,需要另一个伟大的对手来成就,感谢林丹,感谢李宗伟世界会记住你们。晚安。

接下来的文章讲解一些关于js面向对象的东西,敬请关注我的专栏 《前端杂货铺》


Recommend

  • 144
    • 掘金 juejin.im 6 years ago
    • Cache

    前端面试之webpack篇

    还是以前一样,有些概念面试可能会考,我都用*标记了出来,两句话就总结清楚其余的地方如果你想了解webpack,就仔细看看,虽然本教程不能让你webpack玩的很6,但是懂操作流程够了。面试你一般问你webpack的原理,Loader的原理,你有用那些优化措施前

  • 94
    • 掘金 juejin.im 6 years ago
    • Cache

    前端面试之js相关问题(二)

    上一篇我们讲到了,在前端面试的时候常被问到的函数及函数作用域的问题。今天这篇我们讲js的一个比较重要的甚至在编程的世界都很重要的问题 面向对象 。在JavaScript中一切都是对象吗?“一切皆对象!” 大家都对此深信不疑。其实不然,这里面带有很多的语言陷阱,

  • 42
    • blog.poetries.top 5 years ago
    • Cache

    前端面试之MVVM浅析

    1.2 vue 实现 todo-list

  • 53
    • blog.poetries.top 5 years ago
    • Cache

    前端面试之组件化

    视图 数据 变化逻辑

  • 54
    • blog.poetries.top 5 years ago
    • Cache

    前端面试之hybrid

    hybrid server 1.2 hybrid 存在价...

  • 56
    • 掘金 juejin.im 5 years ago
    • Cache

    前端面试之BFC

    什么是BFC BFC(块格式化上下文): 是Web页面可视化渲染CSS的一部分, 是布局过程中生成块级盒子的区域。也是浮动元素与其他元素的交互限定区域。 简单理解就是具备BFC特性的元素, 就像被一个容器所包裹, 容器内的元素在布局上不会影响外面的元素。 BF

  • 55
    • 掘金 juejin.im 4 years ago
    • Cache

    前端面试之手写代码

    本系列会从面试的角度出发围绕JavaScript,Node.js(npm包)以及框架三个方面来对常见的模拟实现进行总结,具体源代码放在github项目上,长期更新和维护 数组去重 (一维)数组去重最原始的方法就是使用双层循环,分别循环原始数组和新建数组;或者

  • 22
    • 微信 mp.weixin.qq.com 4 years ago
    • Cache

    前端面试之彻底搞懂this指向

    this是JavaScript中的一个关键字,但是又一个相对比较特别的关键字,不像function、var、for、if这些关键字一样,可以很清楚的搞清楚它到底是如何使用的。 this会在执行上下文中绑定一个对象,但是是根据什么条件绑定的呢?在不...

  • 3

    1.call的实现 第一个参数为null或者undefined时,this指向全局对象window,值为原始值的指向该原始值的自动包装对象,如 String、Number、Boolean为了避免函数名与上下文(context)的属性发生冲突,使用Symbol类型作为唯一值将...

  • 2

    new操作符具体都干了什么?(1) 首先创建了一个空对象。(2) 设置原型,将对象的原型设置为函数的prototype对象。(3) 让函数的this指向这个对象,执行构造函数中的代码(4) 判断函数的返回值类型,如果是值类型,则返...

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK