Javascript面试题(第一部分)

2020-11-13 08:58[73mins] 累积: 250mins

JS篇

1. 严格模式 “use strict” 的优缺点

为什么有严格模式?

说明过去的编程的代码规范不严格,所以要严格模式

JavaScript编程精解书上的第八章里 p95页

严格模式下的let

图1所示: 严格模式的let

// let counter= 0 如果所涉及的绑定已作为全局绑定存在,则严格不起作用,循环仍然会悄悄地覆盖绑定的值.
function canYouSpotTheProblem(){
    "use strict";
    for(counter = 0; counter < 10; counter++){
        console.log("Happy happy");
    }
}

console.log(canYouSpotTheProblem());
// -> ReferenceError: counter is not defined
图1: 严格模式

图1中的counter没有使用let,在严格模式下会报错.所以严格模式是使js变得略微严格了起来,起到更好的一个提示作用。但是如果所涉及的绑定已作为全局绑定存在,则严格不起作用,循环仍然会悄悄地覆盖绑定的值,例如 外部let counter = 0; 如果取消注释则严格模式不起作用。

严格模式下的this [如图2代码片段所示]

// 'use strict' 
// 严格模式下的this
function Person(name){this.name = name}
let ferdinand = Person('Ferdinand')
console.log(name); 
// -> Ferdinand 不在严格模式下,this指向的是全局作用域,所以此对象的属性是全局绑定的.

// -> 在严格模式下: TypeError: Cannot set property 'name' of undefined
图2: 严格模式

图2结果,严格模式之外进行普通函数的调用,this指向了全局范围作用域,因为它不带new关键字地调用构造函数.所以导致它的 this不会引用新构造的对象.而这在严格模式下是不允许的. 很幸运,在class符号创建的构造函数如果被不带new的关键字调用,那么它始终会报错,即使在非严格模式下,也不成问题.此外严格模式还有其他的内容.

严格模式下函数的参数不可以具有多个相同名称 [图3]

// 'use strict'
function person(age,age){
    console.log(age);
}
person(2)
// 严格模式下: Uncaught SyntaxError: Duplicate parameter name not allowed in this context 提示参数名重复

// 严格模式之外: 没有显示结果, undefined是log的返回结果
图3: 参数不可以具有多个相同名称

那么可以从图3看到如果在严格模式下,函数的形参具有相同的名称,那么就会报错.所以一般严格模式可以放在程序的开头来帮助我们检测所有的程序问题,并且提示出来再加上我们主观的去分辨进一步提升了程序的代码健壮性.

总结: 通过3个案例大致了解严格模式在实际中的使用(更多看es6文档)

2020-11-12 23:56 [<30mins], 跑去弄了一阵子简历方面的事所以现在才回来弄题.

2. console.log(0.1 + 0.2) 的结果?

结果不是0.3,而是0.3000000000000000004.
因为JS中两个数相加是以二进制的形式相加的,然而这个十进制小数转为二进制小数的过程中的有限数字超过52位时,会被截断所以会损失精度.

3. == 和 === 符号的区别

双等号表示两边的值相等,如图4所示:双等号代码

console.log(1 == '1'); 
// -> true 双等号只比较值,第二个1虽然是字符串类型,但是值和1是相等的
// -> 双等存在隐形转换可能,而三等不会
图4: 双等号代码

三个等号表示值和类型都要相等,如图5所示:三等

console.log(1 === '1');
// -> false 三等号比较值和类型,因为两个1中第二个1是字符串类型而第一个1是数值类型
图5: 三等

4. 事件冒泡和事件捕获到底有何区别?

JavaScript编程精解书上的第15.4章 p188 的传播有提到, 这里的传播也就是事件传播或事件传递.

事件传播

了解事件冒泡之前需要建立在了解事件传播的基础上
既然是传播那么就会涉及到2个节点以上,而这两个节点是某种层叠关系,可以是父子关系.也就是某节点具有子节点。因此在此节点中注册的处理事件也将接受在其子节点中发生的事件。
如图6所示: 传播的代码片段

 <p>A paragraph with a <button>button</button>.</p>
 <script>
     let para = document.querySelector('p')
     let button = document.querySelector('button')
     para.addEventListener("mousedown", () => {
         console.log("Handler for paragraph.");
     })
     button.addEventListener("mousedown", event => {
         console.log("Handler for button.");
         // if (event.button == 0) { event.stopPropagation(); }
     })
 </script>
图6: 传播的代码片段

执行图6的代码后,在浏览器可以看到,当你点击段落中的按钮,则段落上的事件处理程序也将收到单击事件。在控制台中先输出"Handler for button.“后再输出"Handler for paragraph.”。这就是事件传播,下面就可以聊事件冒泡和捕获。

事件冒泡和事件捕获

而这里继续刚刚的例子,因为你点击的是段落上的按钮,此时图6的代码中显示段落和按钮都有一个处理程序,因此段落上的按钮的处理程序会首先执行。这被称为是事件向外传播。具体的说,从它发生的节点向该节点的父节点和文档的根目录传播。最后,在特定节点上注册的所有处理程序都轮到后,在整个窗口注册的处理程序才有机会响应该事件。
现在回到题目中了,事件冒泡,刚刚我们讲到的事件向外传播指的就是事件冒泡,事件冒泡的定义:从下至上,当给父子元素用同一事件绑定方法时,触发子元素身上的事件,执行完毕之后,也会触发父级元素相同的事件。

,探讨事件冒泡的好/坏处及为什么要阻止它和如何阻止它.
推荐文章 JS事件冒泡与捕获涵盖应用场景,这是面试考点.
现在继续来看了 2020-11-13 07:46 [ < 30mins],

由于作者他在这篇文章中JS事件冒泡与捕获,使用的例子是vue框架起手,所以我需要用vue来说明了。如图7所示:冒泡和捕获的测试代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        .fatherBox {
            width: 100px;
            height: 100px;
            border: 1px solid #000;
        }
    </style>
</head>

<body>
    <div id="app">
        <div @click="log(1)" @click.capture="log(1)" style="background-color:#00f;" class="fatherBox">
            <div @click="log(2)" @click.capture="log(2)" style="background-color:#66f;">
                <div @click="log(3)" @click.capture="log(3)" style="background-color:#ccf;">
                    <div @click="log(4)" @click.capture="log(4)" style="background-color:#fff;">
                        点击这里
                    </div>
                </div>
            </div>
        </div>
    </div>
    <script src="./vue开发版包文件/vue.js"></script>
    <!-- 这个压缩包:./vue开发版包文件/vue.js 是需要到vue的官网去下载后引入进来的,因为用的是vue来测试该案例 -->
    <script type="text/javascript">
        var vm = new Vue({
            el: '#app', 
            data() { return {}},
            methods: {
                log: function (x) { 
                    console.log(x);
                    // -> 这里是使用vue的 mehtods方法来绑定一个log的函数,方便传值调用
                 }
            }
        })
    </script>
</body>
</html>
图7:冒泡和捕获的测试代码

你可以复制上面的代码然后到编辑器运行,当然这里需要引入包,我有备注在代码中如何去引入。这里补入一个vue官方文档的引用包,直接下载后引入到script标签即可,如附件图1:vue开发版本包

然后当你使用上面的代码后,你去点击浏览器页面的"点击这里"的文字后,会看到如图8所示:输出结果

图8:输出结果

可以看到 @click.capture=“log(1)” 是捕获事件的过程而且会从上至下依次先触发,直到我们点击的按钮4输出来, 此刻又要冒泡事件依次触发回去,也就是又从4开始从下往上。所以输出结果: 1-2-3-4-4-3-2-1
在这里再截一个图可以方便理解: 如图9所示:冒泡和捕获顺序

在这里插入图片描述

图9:冒泡和捕获顺序
到这里8个监听器已经触发了,那么接下来是去掉部分监听器来加强对冒泡和捕获的一个认识.方便代码,所以就对齐了下。 如图10所示:取掉部分监听器
<div id="app">
 <div                     @click.capture="log(1)" style="background-color:#00f;" class="fatherBox">
     <div @click="log(2)"                          style="background-color:#66f;">
         <div @click="log(3)"                        style="background-color:#ccf;">
             <div              @click.capture="log(4)" style="background-color:#fff;">
                 点击这里
             </div>
         </div>
     </div>
 </div>
</div>

点击之后的输出结果为 1-4-3-2 如图11所示:结果输出

图11:结果输出

依旧是先从上往下,1-4 捕获 然后3-2 冒泡的顺序执行.

添加两种监听的方法

在不使用任何框架的情况,我们在js中通过addEventListener方法给Dom添加事件监听。这个方法有三个参数可以传递addEventListener(event,fn,useCapture)。event是事件类型click,focus,blur等;fn是事件触发时将执行的函数方法(function)/ 处理程序;第三个参数可以不传,默认是false,这个参数控制是否捕获触发。所以我们只穿两个参数时,这个事件是冒泡传递触发的 从下往上 / 从内往外,当第三个参数存在且为true时,事件是捕获传递触发的从上往下 / 从外往内。

阻止传递 [阻止捕获为例]

那么到这里,就是说,难道关于这类传播就不能够阻止吗?答案是可以的。
在不使用任何框架的情况下,我们在js中通过stopPropagation方法阻止事件继续传递。使用框架时可使用对应的框架提供的方法。接下来我将了Vue框架的stop修饰符来阻止事件传递,如图12所示:阻止传递

在这里插入图片描述

图12:阻止传递

图9输出结果为1-2-3, 这里为什么叫阻止传递而不叫阻止冒泡或阻止捕获,因为阻止传递包含了两者,当然这里也可以说是阻止捕获,除非不设置捕获事件,那么就叫阻止冒泡.

阻止冒泡

为了进一步探讨,我们来研究阻止冒泡,上面讲的例子是属于阻止传递中的阻止捕获,如图13所示:阻止冒泡

在这里插入图片描述

图13:阻止冒泡

图13的结果输出为 4-3, 因为我把所有的事件捕获都清除了,所以冒泡从下而上,但是我在3的位置设置了.stop所以也就在3的时候不再继续往上冒了.

讲到这里事件冒泡和事件捕获的区别也就出来了.
然后因为事件传播中,面试官可能会问到应用场景

应用场景

我们在使用中多数情况下只使用冒泡监听。例如一条购物车信息,在这条信息中,右下角有一个删除按钮。点击这条消息可查看详情,点击删除按钮可将此商品移除。我们会分别给信息的div和删除button添加一个冒泡的click事件监听。如果不做阻止传递,点击删除button后,会显示商品详情。显然这不是我们想看到的。这时我们给button一个阻止事件传递的功能,点击删除按钮后,事件就会结束,就不再显示商品详情。

好了就讲到这里,这边就答题结束了这个知识点,当然以后会写用原生js的事件传递和事件阻止的例子.
准备开始下一道题了,2020-11-13 11:21[ < 30mins]

5. JS数据类型

为什么要学数据类型?

这个世界许许多多的事物体积各种各样,因此被抽象成了数据,这样方便传输到世界各个角落,也因此既然有了数据,那么意味着需要去读取,修改和创建数据。那么数据在电脑内部表现是什么,这些数据都存储在一连串长的比特序列. 比特(二进制) 是任何一种二值化的东西,描述为0和1.那么0和1的序列是哪来的,是通过高低电压,信号强或弱,明或暗等的形式表示。为了便于使用就简化为0和1,进而用比特表示。
那么13是一个数据,该如何用二进制去表示,首先13本身是一个十进制,但是现在要用二进制的方式去表达,也就是回到用0和1的方式去表达13这个数据。如图14所示:二进制表示13

  0    0    0    0   1   1   0   1
123   64   32   16   8   4   2   1
图14:二进制表示13

用二进制表示13结果为00001101,因为它的非零数码分别为8,4,1,加起来就是13. 每个二进制位从右到左依次增加2倍

这里要说到数据存储的信息是比特为单位的,因为存的时候为了不使这么多位丢失,所以就需要将它们分成代表信息片段的块,而这些块在javaScript中称为值。所有的值都是由二进制位组成,但它们扮演不同角色。有的值是数字,有的是文本,有的是函数。

准备去吃个午饭,2020-11-13 11:43[ 21mins] 。。。 又没保存,不过这次丢失的不是很多还好.

数据类型

然后这里有一篇文章是关于 JS基础篇1:数据类型(8种)的。里面讲的也是蛮详细,不过我要会在这里复述一下,喜欢的小伙伴可以去给该作者点个赞.

在JS中,有ES5,ES6,现在有更高的ES版本了。但是这里先说ES5和6
在ES5中,JS的基本数据类型有 6种分别是: number,string,boolean,undefined,null,object
直到ES6的时候,增加了另外一种Symbol. 这种类型的对象永不相等,即始创建的时候传入相同的值,可以解决属性名冲突的问题,做为标记。因为我本人也具体没使用过这类型的值,所以以后会讲下。
此外,谷歌67版本还出现一种bigInt。是指安全存储,操作大整数。
所以归类下来:JavaScript一共有8种分别是 Number、String、Boolean、Null、undefined、object、symbol、bigInt。
其中JS数据类型:Object 中包含了哪几种类型?
分别是Data,Array,function这三种常规使用的。

JS基本类型和引用类型

基本类型(单类型):除Object。 String、Number、boolean、null、undefined。
引用类型:object包含function、Array、Date

检验数据类型的方法

1. typeof

那么数据类型既然有如此多的类,那么就需要有检查的工具,这样才能确保我们不会搞错。所以在javaScript中有 typeof 可以用来检测,如图

console.log(typeof [])
// -> object
console.log(typeof {})
// -> object
console.log(typeof console.log);
// -> function
图14: typeof检测类型

另外一张图是检测其他数据类型的结果显示,如图15所示: typeof检测类型
在这里插入图片描述

图15: typeof检测类型
typeof判断一个变量是否存在

此外typeof可以用来判断一个变量是否存在,如附图1所示

var a = 5;
console.log(typeof a);
// -> 如果没有定义a就会出现 undefined

// typeof获取一个变量是否存在
if (typeof a != "undefined") console.log('存在');
// -> 注意: 带引号的undefined, 如果不等于"undefined" 就证明a存在
// -> 这么做就不需要通过程序去报错的形式
附图1: typeof鉴定变量是否存在
typeof局限性

但是typeof的局限性是对于Array和Null都会返回object,所以会模糊表达

关于NaN的探讨

可以看到NaN是属于Number类型的,因为NaN是Number中的特殊值。

如图16所示: NaN的探讨

console.log(Number('as') == NaN)
// -> false; 因为 Number('as') 将其转为NaN
// 在js中NaN是不等于NaN的, NaN是非数值型

console.log(isNaN('123'));
// -> false

console.log(isNaN(123));
// -> false

console.log(isNaN('as'));
// -> true 表示是NaN才会返回true

console.log(typeof NaN === typeof NaN);
// -> true
图16: NaN探讨

从图16可以知道, NaN是不等于NaN的值的.但是类型是相等的,这是js规定的, 那么有人会问 Number(‘123’) ,这个结果是123.所以123 也不等于 NaN。

其实基本数据类型已经介绍完有几种了,但是鉴定数据类型的方法还不止typeof 还有其他这里会先暂时不聊

先到这里了 2020-11-13 15:29[ 120mins] 我忘记了什么时候开始的了大概2小时有了, 这篇数据类型的文章还有许多是值得去读的,下次我在回来继续看了.

现在继续开始了,今天把文章润色一下 2020-11-14 07:36 [< 20mins]
2020-11-14 08:18 [44mins] 优化文章到了JS数据基础
2020-11-14 10:44 [< 30mins] 继续刷两道题

2. 检测数组类型的方法
instanceof 操作符

已经有typeof 检测类型,为什么还要instanceof? 在这篇文章中有说明instanceof和typeof区别
因为instanceof返回true和false,而typeof不能,它返回的是对应的字符串。

书中第第六章 p82谈到 instanceof二元运算符

instanceof可以知道一个对象是否来自一个特定的类. 如图17所示

class SymmetricMatrix {constructor(age){this.age = age}}

console.log(new SymmetricMatrix(2) instanceof SymmetricMatrix)
// -> true
// -> 还可以查看继承的类型,如果是那么也会返回true

console.log([1] instanceof Array);
// -> true 同样的应用于Array的标准构造函数,几乎每个对象都是Object的一个实例
// 这里的[1] 相当于做了一步操作 var a=new Array(1); 类似 a instanceof Array
console.log([1] instanceof Object);
// -> true 
// 因为 Array 是 object 的子类
图17: instanceof 鉴定特定的类

所以可以看到 instanceof这个二元运算符可以用来鉴定数组

对象的constructor属性

constructor的定义

在这篇文章有说到JavaScript constructor 属性详解
对象的constructor属性用于返回创建该对象的函数,也就是我们常说的构造函数。
在JavaScript中,每个具有原型的对象都会自动获得constructor属性。除了arguments、Enumerator、Error、Global、Math、RegExp、Regular Expression等一些特殊对象之外,其他所有的JavaScript内置对象都具备constructor属性。例如:Array、Boolean、Date、Function、Number、Object、String等。

那么如何使用这些定义,看到内置对象 Array,如图18所示:constructor 构造器鉴定数组的方式

let arr = ["1","2"];
console.log(arr.constructor);
// -> ƒ Array() { [native code] }
console.log(arr.constructor === Array);
// -> true 

let test = '1'
console.log(test.constructor === Array);
// -> false 
console.log(test.constructor === String);
// -> true
图18: constructor 构造器鉴定数组的方式
Array.isArray( ) 检验值是否为数组

这是内置的标准构造函数的内置方法,用于检验值是否为数组,如图19所示

console.log(Array.isArray([1]));
// -> true
console.log(Array.isArray('1'));
// -> false
图19所示: 检验值是否为数组

基本鉴定的方法就到这了,还有一些封装的方式没有说到
2020-11-14 11:51 [67mins] 因为上一道数据类型的题一直扩展到今天,然后后面要将原型了,

6. 简述javascript原型、原型链?有什么特点

2020-11-15 14:23 [<25 mins]
为什么要学习 javascript原型,原型链, 我先看下书籍吧
在书上的 6.3 原型有说到,第4个🍅到 2020-11-15 16:53 [2h+30mins休息]

看完书中的介绍,原型就是对象的后备属性,如果对象收到一个对它自己没有的属性的请求时,它就会去原型搜索该属性,然后原型以此类推原型的原型一直往后查找。 举书中的例子,如图20所示: 对象原型的toString方法

    <script>
        let empty = {};
        console.log(empty.toString);
        // -> ƒ toString() { [native code] }
        // -> function toString(){...} 说明该属性是一个方法

        console.log(empty.toString());
        // -> [object Object] 
    </script>
图20: 对象原型的toString方法

对于 [object Object] 的解释文章object Object的解释

第一个object表示变量是对象,不是值类型. classname表示该对象的类名。 [object Object] 前面一个是对象,后面一个表示它是Object类的.

那么谁是这个空对象的原型?它是最大的祖先原型,几乎所有对象背后的实体Object.prototype. 如图21所示:最大的祖先原型

console.log(Object.getPrototypeOf({}) == Object.prototype);
// -> true

console.log(Object.getPrototypeOf(Object.prototype));
// -> null
图21: 最大的祖先原型

如图21所示,Object.getPrototypeOf 返回一个对象的原型. 我记得我有画过一张原型图,在文件中找到了,如图22所示:原型图关系

在这里插入图片描述

图22:原型图关系

可以看到JavaScript对象的原型关系形成一个树形结构-这个结构的根是Object.prototype. 它提供了一些在所有对象中显示的方法,例如toString,它将对象转换为字符串表示形式. …树形结构可以参考这篇文章树形结构

许多对象不直接将Object.prototype作为其原型,而是使用另一个提供不同默认树形集的对象.函数派生自 Function.prototype, 数组派生自Array.prototype,如图23所示:Function和Array的prototype

console.log(Object.getPrototypeOf(Math.max) == Function.prototype);
// -> true
console.log(Object.getPrototypeOf([]) == Array.prototype);
// -> true

console.log(Array.prototype);
// -> 里面有push等方法就是放在这里
console.log(Function.prototype);
// -> ƒ () { [native code] }
图23:Function和Array的prototype

这样的原型对象本身就一个原型,通常是Object.prototype,所以它仍然简洁提供像toString这样的方法,注意 Arryay.toString() 和 Object.toString() 是不一样的

原型是可以创建的,通过object.create创建具有特定原型的对象, 如图24所示: 使用object.create创建特定原型的对象

let protoRabbit = {
    speak(line) {console.log(`The ${this.type} rabbit says '${line}'`)}
};
let killerRabbit = Object.create(protoRabbit)
// -> 创建了一个具有特定原型的对象
killerRabbit.type = "killer";
killerRabbit.speak("SKREEEE!");
// The killer rabbit says 'SKREEEE!'
图24:使用object.create创建特定原型的对象

可以看到speak是间歇的方法树形,并赋予speak一个函数作为其值.
‘proto’ 兔子充当了所有兔子共享属性的容器. 而killerRabbit就是这样的单个兔子对象包含仅适用于自身的属性-在这种情况下是它的类型 ,并从其原型派生共享属性.

然后回到主题 原型,原型链及特点

通过图22:原型图关系可以清楚的看到, 每个构造函数(Array为例) 都有一个protoype属性指向一个对象,而这个对象就是原型.
那么每个构造函数的实例都有一个__proto__属性指向它的原型,从而获取原型的属性和方法,而该原型也有__proto__指向它自己的原型,依次类推,直到最顶端null,这个串成链的过程就叫原型链.
那么原型的特点:就是实现继承一个对象可以拿到另外一个对象的的属性和方法,进而达到节省内存空间.
掌握原型的3个规则

  1. 构造函数都一个prototype属性指向原型
  2. 原型对象都有一个constructor属性指向构造函数
  3. 构造函数new实例化的实例对象都有一个__proto__指向原型.

今天就到这里了
准备开始JS的第4个🍅

7. javascript中的作用域和变量声明提升

关于JS的作用域是非常常考的内容. 在书上3.2中提及到

为什么要学习作用域, 因为需要了解好作用域,这样在使用函数的时候才能游刃有余. 作用域分为 全局和局部.

全局作用域

全局作用域是整个程序意味着你可以在任何地方引用此类绑定

局部作用域

局部作用域是函数参数创建的绑定或在函数内声明的绑定只能在此函数中引用

局部作用域在函数中的体现,每次函数调用的时候就会创建这些绑定的新实例. 这在函数之间提供了隔离,也就是不会因为变量名相同的问题产生一些冲突. 每个函数调用都在它自己的小世界(局部作用域)中运行,而且可以在不了解全局环境中发生.

let count var

这是3个频繁使用的绑定方式, 在2015年的js中, 只有函数才能创建新的作用域,因此使用var关键字创建的绑定在它们的整个函数中是可见的,如果不在函数中那么就是在全局作用域中可见,而 let和count却不同,它们声明的绑定在它们声明它们的块中局部可见,如果在循环中创建let和count中的一种绑定,那么循环前和后的代码都看不到它们. 如图25所示: let var 的使用

let x = 10;
if(true){
    let y = 20;
    var z = 30; // z未在函数中,所以它的作用域是全局的
    console.log(x + y + z); 
    // -> 60
    // -> 这里的x是会往外查看包围它的作用域,因此x在示例中的块内部是可见的.
}

// console.log(y); 
// -> y is not defined
// 因为y是使用let声明的绑定,具有局部特点.

console.log(x + z);
// -> 40 
// -> var 在2015的js中要么在函数内要么在函数外.

function text(){var n = 5}
// console.log(n);
// -> n is not defined
// 因为var 如果在函数内绑定,那么函数外部是访问不到该绑定的.
图25:let var 的使用

这里的难点是 console.log(x + y + z); 其中每个作用域都可以‘查看‘包围它的作用域,因此x在示例中的块内部是可见的, 然后z因为没有在函数中声明所以它是全局可见的.
也有特殊的情况, 如图26所示多个绑定具有相同名称.

function fn(n){return n * 2}
var n = 10;
console.log(fn());
// -> NaN 因为此时 函数中的形参n 为undefined.
console.log(fn(11));
// -> 22 当函数fn内的代码引用n时,它会看到自己的n,而不是全局的n
图26:绑定具有相同名称

当函数fn内的代码引用n时,它会看到自己的n,而不是全局的n.
注意: 此时 函数中的形参n 为undefined.

为了更好的理解刚才的例子,我们将函数的形参去除,如图27所示, n会找包围它的外部的作用域,应证了一句话是每个作用域都可以‘查看‘包围它的作用域.

function fn(){
    return n * 2
}
var n = 10;
console.log(fn());
// -> 20 每个作用域都可以‘查看‘包围它的作用域. 
console.log(fn(11));
// -> 22 当函数fn内的代码引用n时,它会看到自己的n,而不是全局的n
图27:绑定具有相同名称

从图27中可以看到,虽然fn(11) 传了实参进去,但是函数fn并没有设置形参来接受.所以函数内的n会寻找外部的值为10的n,很重要这里在面试题中是十分容易混淆的.

嵌套作用域

多度局部这个名字听起来高级了许多, 就是其他块和函数内创建块和函数,从而产生多个度的局部.为什么要说这个,因为在后期的复杂结构中会出现很多这类使用方式,所以对作用域的了解必须深入探讨,如图28所示:多度局部

const fn = function (n) {
    // let n = n || undefined
    // console.log(Boolean(n || undefined));
    const fn1 = function (a, b) {return n * a * b}
    return fn1(3, 4)
}
console.log(fn(2));
// -> 24
图28:多度局部

词法作用域

在图25中有提到 每个作用域都可以‘查看‘包围它的作用域,在这里因为在局部与局部之间显示作用,所以用了一个新的术语称为词法作用域.我们可以看到函数fn1可以看到外部函数的n的绑定,但是局部绑定的a和b是外部看不到的. 所以每个局部作用域可以查看包含它的所有局部作用域,并且所有作用域都可以看到全局作用域在并且…我们通过图29来加深理解.

const fnOut = function (n) {
    const fn = function () {
        // let n = n || undefined
        // console.log(Boolean(n || undefined));
        const fn1 = function (a, b) {
            return n * a * b
        }
        return fn1(3, 4)
    }
    return fn()
}
console.log(fnOut(2));
// -> 24
图29:词法作用域

从图29中可以看到,每个局部作用域可以查看包含它的所有局部作用域,并且所有作用域都可以看到全局作用域 因为fn1函数内部的n引用了最外部函数fnOut调用时传入的n.

我们再来进一步加强… 并且所有作用域都可以看到全局作用域 这后半句,如图30所示:函数内部作用域会一直往外找

let n = 10 // 这里的n因为fn1的调用中的n一直往外找,找到的.
// 如果这里的n也没有定义,那么会显示n is nodefined.
const fnOut = function () { // 如果这里有n 那么是NaN
    const fn = function () {
        const fn1 = function (a, b) {
            return n * a * b
        }
        return fn1(3, 4)
    }
    return fn()
}

console.log(fnOut());
// -> 120
图30所示:函数内部作用域会一直往外找

变量提升

基本在这里作用域已经立即的很透彻了. 接下来是变量提升的相关话题,如图31所示.

foo;  // undefined
var foo = function () {
    console.log('foo1');
}
 
foo();  // foo1,foo赋值

// var foo;
// foo // undefined
// foo = function(){ ... }
// foo() 
图31: 变量提升

在作用域中,变量提升属于预解析的内容. 也是考点之一,后面会更多的补充,简单的说就是所有的声明(变量和函数)都会被移动到各自作用域的最顶端.

8. 谈谈this对象的理解,call()和apply()的区别

来了,this是真的非常重要,甚至在vue中也是经常涉及到this,而且面试也是必考的,考官会通过这个知识点从上到下把你diss一遍😄,那么先来看下相关的介绍,为什么我们要去学this,目前我看的那本书上很少专门去提及或者是我还没阅到.所以我打算看篇文章来聊聊this. this的应用场景总结
此外我自己写了一篇,欢迎来看. this的应用场景

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值