资讯专栏INFORMATION COLUMN

高级JS试题

he_xd / 2141人阅读

摘要:本文是高级入门的配套试题,题目解析的部分内容将引用高级入门。接收一个对象,并由这个对象去调用函数。循环内,调用函数,并设置第二个参数的值为。所以相当于执行而的作用是往消息队列中存放一个回调函数,并在特定时间间隔后执行它。

本文是JS高级入门的配套试题,题目解析的部分内容将引用JS高级入门

题目一(this指针)
    function logName(){
        console.log(this.name);
    }
    function doFun1(fn){
        fn();
    }
    function doFun2(o){
        o.logName();
    }
    var obj = {
        name: "LiLei",
        logName: logName
    };
    var name = "HanMeiMei";
    doFun1(obj.logName);
    doFun2(obj);
解析

题目首先定义了三个函数:
【】logName:打印调用者(即谁去调用它,通常是一个对象)的name属性。
【】doFun1:接收一个函数,并直接运行这个函数。
【】doFun2:接收一个对象,并由这个对象去调用logName函数。
而后定义了两个变量:
【】obj是一个对象,里面定义了name(假设另命名为_name)的字符串变量。定义了logName(假设另起名字为_logName),指向外部的logName函数。
【】name是一个字符串变量。
最后分别对两个函数进行调用,
【】doFun1(obj.logName)。

向函数传递了obj对象内部的_logName,而_logName是指向logName的。所以实际上doFun1接收的是指向logName函数的变量。即等价于doFun1(logName)。

而在doFun1内部是直接执行logName的。没有明确的调用者。则这时等价于由window对象去调用,等价于window.logName。

而再由于所有在全局作用域(注意仅仅是全局作用域)中定义的变量都是window对象下的变量。都可以通过window对象进行访问。
所以这时访问到的就是name。

所以输出的是HanMeiMei。

【】doFun2(obj)。

传递给doFun2的是obj的地址值,即doFun2中的o指向的就是obj,等价于obj。

o.logName是由o去调用logName。相当于obj.logName。

所以找到的是obj内部的name(即我们假定的_name)。

所以打印出的是LiLei。

答案
HanMeiMei
LiLei
题目二(this指针的修改apply)
    function fun(somthing) {
        console.log(this.name, somthing);
    }
    function bindFun(fn, obj) {
        return function () {
            return fn.apply(obj, arguments);
        }
    }
    var obj = {
        name: "LiLei"
    };
    var bar = bindFun(fun, obj);
    var b = bar("HanMeiMei");
    console.log(b);
解析

本题的主要考点是this的相关函数apply。
首先定义了三个函数:
【】fun函数:接收变量,并且打印这个函数的调用者的name值,以及形参something。
【】bindFun函数:接收两个参数,分别是函数以及对象。bindFun没有逻辑操作。只是返回了一个匿名函数(我们假定为_fun)
【】_fun函数:同样没有操作,直接返回一个已经通过apply修改了this的函数的执行结果。(即直接返回一个函数执行结果值,而这个函数的this的值是已经被修改过的)
而后定义了三个变量:
【】obj是一个对象,里面只定义了name字符串变量。
【】bar是一个变量,这个变量的值为bindFun函数执行的结果值。
【】b是一个变量,值为bar函数执行的结果值。
整段代码中,除了定义,一共只执行了3句逻辑语句:
【】在bindFun函数的执行,传递了两个函数,分别是fun函数,obj对象。bindFun函数执行结果是返回一个函数_fun。所以bar指向_fun函数。
【】bar函数的调用,即相当于_fun函数的调用。

_fun函数的返回fn.apply(obj, arguments)的运行值;

其中这里的fn是bindFun函数接受的第一个参数fun,即返回的是fun函数。

而这个fun函数进行了apply的函数调用,修改了函数中this的值。

this指向为bindFun函数的第二个参数值,即外部的obj变量。

apply函数还接受了第二个参数arguments,即_fun函数的参数数组。即bar函数的参数数组【"HanMeiMei"】。作为fun函数的参数。

apply函数的作用是马上执行调用者函数,即马上执行bar(即_fun,即fun)函数。

fun函数执行是打印this.name,和参数something。this指向obj,所以this.name为LiLei。而something值为【"HanMeiMei"】

所以打印了,LiLeiHanMeiMei。并且没有返回值。

【】b是bar函数的返回值,然而bar函数并没有返回东西,所以是undefined。所以console.log(b)打印的值是undefined。

绕了一圈,整个过程相当于执行。fun.call(obj,"HanMeiMei");

答案
LiLeiHanMeiMei
undefined
题目三(自执行函数)
function logName() {
    console.log(this);
    console.log(this.name);
}
var name = "XiaoMing";
var LiLei = { name: "LiLei", logName: logName };
var HanMeiMei = { name: "HanMeiMei" };
(HanMeiMei.logName = LiLei.logName)();
解析

这题相对简单一些。只考到自执行函数的定义。并没有借助他的特性做代码隔离。只是想考一下是否已经掌握了自执行函数的定义。
首先定义了一个函数:
【】logName,打印出调用者(即this指针的指向),已经调用者的name属性值
而后定义了三个变量:
【】name:字符串变量,由于全局的,所以相当于window对象的属性值。
【】LiLei:定义了对象,该对象包含name值,以及logName(我们假定LiLei的logName重新命名为_logName)。指向外部的logName函数
【】HanMeiMei:定义了对象,该对象只包含name值。
最终只执行了一条语句,两个步骤。
分别是赋值语句:

为HanMeiMei对象添加一个参数logName,赋值为LiLei的_logName。即指向外部的logName。这不是重点,重点是赋值语句的结果是什么?
是所赋的值,即赋值语句的结果是LiLei.logName,即HanMeiMei.logName = LiLei.logName终止得到的是logName。

然后对这个函数进行调用。即logName(),这时this没有发生改变,还是window,所以输出是打印window对象。已经window.name即XiaoMing。

答案
XiaoMing
题目四(声明提前)
    test();
    var test = 0;
    function test() {
        console.log(1);
    }
    test();
    test = function () {
        console.log(2);
    };
    test();
解析

声明提前的题,把背后的代码执行顺序理顺就好。

首先将声明都放到代码的最上面:

var test;//定义变量

function test(){console.log(1)}//定义函数

然后执行的操作:

test();//函数调用操作

test = 0;//赋值操作

test();//函数调用操作

test = function(){console.log(2)}//赋值操作,将test赋值为函数

test();//函数调用操作

所以上述代码等价于(声明提前,先定义,后执行):

var test;

function test(){console.log(1)}

test();//调用函数,输出1

test = 0;

test();//此时test为0,不是函数,将报错test is not a function

test = function(){console.log(2)}//由于JS报错,后面的代码将不被运行

test();//由于JS报错,后面的代码将不被运行

综合来说,这里设置了三个考点:

声明提前。

函数的定义与函数赋值的区别,function xx为函数定义,将整体上移。

JS报错后,后面的代码将不被运行。

答案
1
Uncaught TypeError: test is not a function
题目五(基本类型与引用类型)
    var name = "LiLei";
    var people = {name: "HanMeiMei"};
    function test1(name) {
        name = "LaoWang";
    }
    function test2(obj) {
        obj.name = "LaoWang";
    }
    test1(name);
    test2(people);
    console.log("name    " + name);
    console.log("name    " + people.name);
解析

题目首先定义了两个函数:
【】test1:接受一个参数,并修改该参数的值。
【】test2:接收一个对象,并修改该对象的属性的值。
而后定义了两个变量:
【】name是一个字符串参数,值为"LiLei"
【】people是一个对象,包含一个name属性。值为"HanMeiMei"
然后分别对两个函数进行调用:
test1(name):即将name作为参数,调用test1函数。

这时函数的内部将产生一个新的参数name(记作_name),它的值等于外部的name的值(LiLei);

test1函数将_name的值修改为"LaoWang",但是由于_name和name是两份独立的变量。所以name的值不受改变。

test2(people):将people作为参数,调用test1函数。

这时函数的内部将产生一个新的参数obj,它的值等于外部的people的值;

而people是引用类型,其值为对象{name:"HanMeiMei"}的地址值。所以obj也为对象{name:"HanMeiMei"}的地址值。

test2对对象{name:"HanMeiMei"}的name属性进行修改,改为"LaoWang"
所以obj和people的name值都发生了改变。

这里涉及到两个知识点

基本数据类型是按值访问的,即该变量就存在了实际值。而引用数据类型保存的是则是对实际值的引用(即指向实际值的指针)。

函数形参(即在函数中实际使用的值,如test函数里面的name)和参数的实参(即往调用函数时调用的参数,如test(name)中的name)的值相同,但并不是"同一个值"(在内存中的地址是不同的,相当于var a = b =0;)。

在函数参数的传递,是通过按值传递的。

答案
LiLei
LaoWang
题目六(JS线程与闭包)
    执行的结果是什么,分别在什么时间输出
    for (var i = 1; i < 5; i++) {
        setTimeout(function () {
            console.log(i);
        }, i * 1000);
     }
解析

JS线程的规则:程序将先把主逻辑的内容做完,再去读取消息列表,调用消息列表中的回调函数

在这里主逻辑为一个for循环,从i为1到i为4循环执行4次for循环的内容。

for循环内,调用setTimeout函数,并设置第二个参数的值为i。注意这里是对setTimeout函数直接进行调用。所以参数中i的值是随for循环改变的。所以相当于执行

setTimeout(function () {doSomething}, 1000);

setTimeout(function () {doSomething}, 2000);

setTimeout(function () {doSomething}, 3000);

setTimeout(function () {doSomething}, 4000);

而setTimeout的作用是往消息队列中存放一个回调函数,并在特定时间间隔后执行它。所以该回调函数会在for循环之后完成。

for循环执行完时,i的值为5。(因为5<5不成立,结束循环)。所以调用回调函数function(){console.log(i)}时,i的值为5。所以输出为5。

闭包是因为回调函数引用到了for循环的i,回调函数没执行完,i不能被回收。所以还是能访问到。

答案
输出4次,每一秒输出一个5
题目七(作用域陷阱)
    function logName(){
        console.log(name);
    }
    function test () {
        var name = 3;
        logName();
    }
    var name = 2;
    test();
解析

一句话可以解释完:作用域的层级关系与函数定义时所处的层级关系相同
注意是,函数定义时的层级关系,而不是调用时的层级关系。
在这里,logName函数,test函数以及外部的name变量(值为2)处于同一个层级。
所以调用logName时,找到的是外部的name变量。
所以打印出2

答案
2
题目八(隐式闭包)
    function logNum(num) {
        console.log("num   " + num);
    }
    for (var i = 0; i < 2; i++) {
        var fun = logNum.bind(null, i);
        setTimeout(fun, 100);
    }
解析

这道题和第六题很像,但是运行结果并不一样。
原因是bind函数产生了隐式闭包。
为什么bind函数能修改this指针?底层实现笔者不知道,但是我们可以利用闭包的特性通过自执行函数来模拟bind函数。
bind(obj,elem0,elem1,elem2...)函数相当于

(function(_obj,_elem0,_elem1,_elem2...){
    return function(){//这个函数就是调用bind函数的函数
        doSomething;
        //将this.替换成_obj.
        //这里将可能使用到_elem0,_elem1,_elem2参数。
    }
})(obj,elem0,elem1,elem2...)

通过自执行函数返回一个函数,形成一个闭包,内部函数调用的参数是自执行函数的参数,而不是外部的元素。

根据前面第五题的解析函数形参(即在函数中实际使用的值,如test函数里面的name)和参数的实参(即往调用函数时调用的参数,如test(name)中的name)的值相同,但并不是"同一个值"(在内存中的地址是不同的,相当于var a = b =0;)。
doSomething中使用到的参数是自执行函数的形参(_elemX),而不是外部的实参elem。
所以外部的elem的变化对doSomething没有影响。

根据解析,对题目进行改造,相当于

function logNum(num) {
    console.log("num   " + num);
}
for (var i = 0; i < 2; i++) {
    var fun = (function(_i){
        return function(){
            console.log(_i);
        }
    })(i);
    setTimeout(fun, 100);
}

而自执行函数是定义完马上执行的。所以拿到的值_i也是随着for循环改变的。
所以输出0 1

利用该特性还可以解决常见的面试题:通过原生JS返回所点击元素索引值。

    var domList = document.getElementsByTagName("div");
    for (var i = 0, length = domList.length; i < length; i++) {
        var item = domList[ i ];
        (function(_item, _i) {
            _item.addEventListener("click", function() {
                console.log(_i);
            })
        })(item, i)
    }
答案
0
1

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/81869.html

相关文章

  • 面试试题总结

    摘要:闲心面试题一任选一题分的区别的区别是无序列表,是有序列表,是定义列表有层次关系。无任何语义,仅仅用作样式化或者脚本化的钩子是有一定语义的,适合有主题性的内容,表示一个专题,一般有标题,但是不可以乱用。诞生于年,由等人创建,后为所收购。 闲心面试题 一、任选一题(5分)a) ul、ol、dl的区别?b) div、section、article的区别? a:ul是无序列表,ol是有序列表,...

    alaege 评论0 收藏0
  • 前端必须要珍藏的技术文章和面试题

    摘要:前端工程师学习资料,快速查找面试题,经典技术文章的总结,编程技巧,帮助学习者快速定位问题花点时间整理出一下前端工程师日常工作所需要的学习资料查找,帮助学习者快速掌握前端工程师开发的基本知识编程始于足下记住再牛逼的梦想也抵不住傻逼似的坚持蝴蝶 前端工程师学习资料,快速查找面试题,经典技术文章的总结,编程技巧,帮助学习者快速定位问题花点时间整理出一下web前端工程师日常工作所需要的学习资料...

    MoAir 评论0 收藏0
  • 高级前端面试题大汇总(只有试题,没有答案)

    摘要:面试题来源于网络,看一下高级前端的面试题,可以知道自己和高级前端的差距。 面试题来源于网络,看一下高级前端的面试题,可以知道自己和高级前端的差距。有些面试题会重复。 使用过的koa2中间件 koa-body原理 介绍自己写过的中间件 有没有涉及到Cluster 介绍pm2 master挂了的话pm2怎么处理 如何和MySQL进行通信 React声明周期及自己的理解 如何...

    kviccn 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<