JavaScript面试题(三)

一、以下代码的输出结果是多少?

for(var i=0;i<5;i++){

        setTimeout(function(){
        console.log(i);
        },1000);
    
    }

结果是:55555
追问:怎样变成01234?

    for(var i=0;i<5;i++){
        (function(i){
            setTimeout(function(){
            console.log(i);
            },1000);
        })(i);
        
        }

二、this指向问题

1、在全局作用域内,this指向window
2、函数内部的this指向函数执行时的直接调用者
3、构造函数中的this指向new关键字创建的实例对象
4、严格模式下没有直接调用者的函数中的this指向undefined
5、用call,apply,bind间接调用函数后,this指向被绑定的对象

补充:
箭头函数中的this指向定义时this所处的宿主对象,而不是执行时调用它的对象

参考文献:
彻底理解js中this的指向,不必硬背。
深入理解ES6箭头函数的this以及各类this面试题总结

三、实现一个倒计时函数,类似于秒杀。

    window.onload=function(){

        var time=Date.UTC(2017,9,10,23-8,0,0);//设定一个倒计时时间点(毫秒)
        var counter=setInterval(function (){
            var temp=Date.now();//获取当前时间(毫秒数)
            if(temp<=time){//如果没有到达倒计时时间点
               console.log(Math.floor((time-temp)/1000));//显示剩余时间
            }else{
                clearInterval(counter);//到达倒计时时间点,清除定时器
            }

        },1000);
    };

分析:
Date.UTC(年,月-1,日,时,分,秒),返回的是当前时区的毫秒数。
Date.now()返回的是GMT时间,即格林威治时间的毫秒数。

四、解释变量作用域

1、变量的作用域 :全局作用域和函数作用域。
2、函数内部可以直接读取全局变量,但是在函数外部无法读取函数内部的变量。

五、模仿块级作用域(自执行函数)

JavaScript中没有块级作用域的概念,但是有函数作用域。因此可以用函数作用域模仿块级作用域
1、方法:定义一个匿名函数(function(){})并立即调用((匿名函数)())。

(function(){
//这里是块级作用域
})();

这样,在匿名函数中定义的任何变量都会在匿名函数执行结束时被销毁。
2、作用创建私有作用域,从而限制向全局作用域中添加过多的变量和函数防止命名冲突

五、闭包

1、定义:能够读取其他函数内部变量的函数。
2、用途 :

①模仿块级作用域(创建并立即调用一个函数)。
②设置访问函数中私有变量的和私有函数的特权方法。
③模块模式:为单例创建私有变量和特权方法。

3、缺点:

①闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成性能问题。
解决方法:在退出函数之前,将不使用的局部变量全部删除
②闭包能够在父函数外面,改变父函数内部变量的值。所以,如果你把父级函数当做对象使用,把闭包当做它的公用方法,把内部变量当作它的私有属性,这时候要注意不要随便改动父函数内部变量的值。

4、如何释放掉闭包中的局部变量?

个人猜想:
父函数返回值为子函数,并将子函数赋给一个变量test。释放掉闭包中的局部变量,就可以将test重新赋值为null。使得没有变量引用父函数的返回的子函数即可。
参考文献:
什么是闭包?闭包的优缺点?
[JavaScript高级程序设计]

六、垃圾收集

一)JavaScript自动垃圾收集机制的原理:

找出那些不再继续使用的变量,然后释放其占用的内存。为此,垃圾收集器会按照固定的时间间隔(或代码执行中预定的手机时间)周期性地执行这一操作。

二)标识无用变量的策略:
1、标记清除

①当变量进入环境时,将变量标记为“进入环境”永远不能释放进入环境的变量所占用的内存
②当变量离开环境时,将其标记为“离开环境”。垃圾收集器会销毁那些带有标记的值,并回收它们所占用的内存空间

2、引用计数(不常用)

原理:跟踪记录每个值被引用的次数。
①将一个值赋给一个声明的变量,这个值的引用次数是1。
②将同一个值赋给另一个变量,则该值的引用次数+1。
③包含这个值引用的变量取得了另外一个值,则该值引用次数-1。
④当这个值的引用次数变为0时,则说明没有办法在访问这个值了,因而可以将其占用的内存空间回收。

--存在问题:循环引用
定义:对象A中包含一个指向对象B的指针。对象B中也包含一个指向对象A的指针。
例如:

function problem(){
var objectA=new Object();
var objectB=new Object();
objectA.someOtherObject=objectB;
objectB.anotherObject=objectA;
}

在这个例子中,objectA和ObjectB通过各自属性相互引用,引用次数都是2。函数执行完毕后,objectA和ObjectB的引用次数不为0,还将继续存在。假设这个函数被重复多次调用,就会导致大量内存的不到回收。

七、Eventloop

在程序中设置两个线程,一个负责程序本身的运行,称为“主线程”;另一个负责主线程与其他进程(主要是I/O操作)的通信,被称为“Event Loop线程”。
每当遇到I/O的时候,主线程就让Event Loop线程去通知相应的I/O程序,然后接着往后运行,不会被当前I/O阻塞。等到I/O程序完成操作,Event Loop线程再把结果返回主线程。主线程就调用事先设定的回调函数,完成整个任务。这种运行方式叫做“异步模式”或“非阻塞模式”。

补充:单线程模式使得JavaScript更简单,占用资源更少。
参考文献:
什么是 Event Loop?

八、函数声明和函数表达式

1、函数声明:

function sum(num1,num2){
        return num1+num2;
}

解析器在向执行环境中加载数据时,会率先读取函数声明,并使其在执行任何代码前可用(函数声明提升)。
例1:

console.log(sum(10,10));
function sum(num1,num2){
        return num1+num2;
}

上例中,可以在sum的函数声明之前调用该函数。
2、函数表达式:

var  sum=function(num1,num2){
        return num1+num2;
};

对于函数表达式 ,必须等到解析器执行到它所在的代码行,才会真正被解释执行。

console.log(sum(10,10));
var sum=function(num1,num2){
        return num1+num2;
};

上例中,代码会报错。因为函数位于一个初始化语句中,而不是一个函数声明。换句话说,在执行到函数所在的语句之前,变量sum中不会保存对函数的引用,而且因为第一行代码就会导致“unexpected identifier”意外标识符错误,实际上也不会执行到下一行。

九、标签的位置

建议把全部JavaScript引用放在元素中页面内容的后面,这样在解析JavaScript代码之前,页面的内容将完全呈现在浏览器中。用户也会因为浏览器窗口显示空白页面的时间缩短而感到打开页面的速度加快了。
如下例所示:
···



Example HTML Page







···
参考文献:
JavaScript高级程序设计

十、延迟脚本和异步脚本

1、延迟脚本:

HTML4.01中引入defer属性。(低版本IE支持)
作用:告诉浏览器立即下载脚本,但使脚本延迟到整个页面都解析完成后再运行。




Example HTML Page







注意:HTML5规范要求多个延迟脚本按顺序执行,但实际情况中延迟脚本不一定按照顺序执行。

2、异步脚本

HTML5中引入async属性。(低版本IE不支持)
作用:告诉浏览器立即下载脚本,但不用等待脚本的下载和执行,可以异步加载页面中的其他内容。

你可能感兴趣的:(JavaScript面试题(三))