JS问题总结

1、作用域链

当定义一个函数时,它实际上创建了一个作用域节点,该节点上存储着当前作用域中的局部变量,并且该节点会挂载在作用域链的底端。在该函数中嵌套定义另一个函数时,同样会创建另一个函数作用域的节点,该节点同样也存储着当前函数作用域中的局部变量,在作用域链中会将该节点挂载在外层函数的节点之下。所以在进行变量访问时,会从自身节点开始查找,如果未找到变量的对应值,则会继续查找上一个节点。而由这一系列节点所串联起来的链就是我们所说的作用域链。

JavaScript中的函数采用静态作用域,也称词法作用域。当在执行函数调用时,不管何时何地执行函数,其中的变量在函数定义时就已经决定了,函数会从自身作用域节点开始,沿着作用域链向上访问变量的值。

注意:作用域链的顶端是全局作用域,作用域链在函数定义时就已经创建了。

  • while 的话只是在函数局部环境或者全局环境运行,并不会改变作用域链。

  • with 当代码运行到with语句时,运行期上下文的作用域链临时被改变了。一个新的可变对象被创建,它包含了参数指定的对象的所有属性。这个对象将被推入作用域链的头部,这意味着函数的所有局部变量现在处于第二个作用域链对象中。

  • try catch 当try代码块中发生错误时,执行过程会跳转到catch语句,然后把异常对象推入一个可变对象并置于作用域的头部。在catch代码块内部,函数的所有局部变量将会被放在第二个作用域链对象中。

  • eval 是把字符串转换为js代码,若果字符串中有新定义 函数,那么它就有可能再建一个执行环境。

2、this的工作原理
  • 使用new实例化对象时,this指向新创建的对象
  • 在全局范围内,this指向全局对象(浏览器下指window)
  • 当用apply和call上下文调用的时候指向传入的第一个参数
  • 如果一个函数中有this,并且这个函数是以对象方法的形式调用,那么this指向的就是调用该方法的对象。
var obj={
   test:function(){
     console.log(this);
  }
}
obj.test();   //obj
  • 如果一个函数中有this,但是它没有以对象方法的形式调用,而是以函数名的形式执行,那么this指向的就是全局对象。
function test(){
 console.log(this);
}
test();   // window
  • 如果一个函数中有this,并且包含该函数的对象也同时被另一个对象所包含,尽管这个函数是被最外层的对象所调用,this指向的也只是它上一级的对象。
var obj={
   test:{
     fn:function(){
       console.log(this);
    }
   }
}
obj.test.fn()  //test
3、函数声明

定义函数有三种方式:

  • function函数:优先解析
  • function 构造函数、函数字面量定义:顺序解析
示例:
    // 4 2 3 3 5 6
   function f(){return 1;}                 // 函数1     优先解析
    alert(f());        //返回值为4 说明第1个函数被第4个函数覆盖    
   var f = new Function("return 2;");        // 函数2 
    alert(f());        //返回值为2 说明第4个函数被第2个函数覆盖
   var f = function(){return 3;}            // 函数3 
    alert(f());           //返回值为3 说明第2个函数被第3个函数覆盖    
   function f(){return 4;}                 // 函数4    优先解析 覆盖函数1
    alert(f());        //返回值为3 说明第4个函数被第3个函数覆盖
   var f = new Function("return 5");         // 函数5 
    alert(f());    //返回值为5 说明第3个函数被第5个函数覆盖    
   var f = function(){return 6 ;}            // 函数6 
    alert(f());        //返回值为6 说明第5个函数被第6个函数覆盖    

当声明了多个同名函数时, 后面的函数会覆盖前面的函数

4、变量提升,但是函数表达式不会提升。

运行一下函数

(function() {
   var x=foo();
   var foo=function foo() {
      return “foobar”
    };
   return x;
})();

语句中变量的声明会提升,但是定义不会提升。以上代码等同于:

var foo;
var x = foo();
foo = function foo() {...}

当执行到 x = foo() 时,由于foo未被定义为函数,所以会返回

TypeError: foo is not a function
5、javascript中函数和声明的变量执行顺序
  • javascript 函数声明和变量声明会被解释器提升到最顶端。
  • 同一标识符,函数声明优先于变量声明
var a; // 声明一个变量,标识符为a
function a() { // 声明一个函数,标示符也为a
}
alert(typeof a);     //" function"

-----------------------------------------------------------------------------------------------
function a() {
}
var a = 1; // 注意此处
alert(typeof a);      //  "number"

真正执行顺序
function a(){}
var a;
a=1;
alert(typeof a);
6、contextmenu

contextmenu 是当浏览者按下鼠标右键出现菜单时或者通过键盘的按键触发页面菜单时触发的事件。在页面中的 中加入 onContentMenu="return false" 就可禁止使用鼠标右键了 。

7、将数组 arr 中的元素作为调用函数 fn 的参数

要求输入 :

function (greeting, name, punctuation) {
    return greeting + ', ' + name + (punctuation || '!');
}, ['Hello', 'Ellie', '!']

结果输出 :

Hello, Ellie!

执行代码:

function argsAsArray(fn, arr) {
  return fn.apply(this,arr); 
}

解析:
调用函数可以使用call或者apply这两个方法,区别在于call需要将传递给函数的参数明确写出来,是多少参数就需要写多少参数。而apply则将传递给函数的参数放入一个数组中,传入参数数组即可。这里的this指向window,但是function里没有用到this,也就是说function里没有会根据作用域不同而需要指定的this的值,所以这里apply的第一个参数放什么都可以,null也行.

8、将函数 fn 的执行上下文改为 obj 对象

要求输入 :

function () {return this.greeting + ', ' + this.name + '!!!';}, 
{greeting: 'Hello', name: 'Rebecca'}

结果输出 :

Hello, Rebecca!!!

执行代码:

function speak(fn, obj) {
 return fn.apply(obj);
}

解析:

9、AMD和CMD的区别

两者的区别在于对模块的加载和执行方式不同,AMD会在加载完模块的同时去执行模块,从而拥有延迟低、效率高的特性;CMD则是加载完所有依赖模块后,再进入程序,遇到需要执行的模块才会执行相应的操作。

requireJS是基于AMD规范实现的模块加载器

seaJS是基于CMD规范实现的模块加载器

10、改变执行顺序的基础函数:setTimeout和setInterval函数

这两个函数在执行时会被压入事件循环队列当中,在当前作用域下的所有程序都执行完成后,才会开始执行排列在事件循环队列当中的函数,所以这两个函数能够改变程序的执行顺序。
来看例子:

function test(){
  console.log(1);
  setTimeout(function(){
    console.log(2);
  },0)
 setTimeout(function(){
    console.log(3);
  },100)
 setTimeout(function(){
    console.log(4);
  },200)

console.log(5);
}

test()    //15234
11、下面代码输出结果
(function(){
    var a=b=3;
})()

console.log(typeof a);  //undefined
console.log(typeof b);   //number

var a=b=3的声明是以下代码的简写

b=3;   // 成为一个全局变量
var a=b;
12、arguments 转化成数组问题

arguments并非正真的数组,所以不能使用数组带有的方法,把arguments转化成真正的数组有两种方法:

  • 使用slice方法,此方法返回一个数组
var args=Array.prototype.slice.call(arguments);  
  • 循环遍历逐一填入新数组。
    var args=[];
    for(var i=1;i
13、二次函数封装

实现函数 partialUsingArguments,调用之后满足如下条件:
1、返回一个函数 result
2、调用 result 之后,返回的结果与调用函数 fn 的结果一致
3、fn 的调用参数为 partialUsingArguments 的第一个参数之后的全部参数以及 result 的调用参数

输入 : 无
输出 : 无

代码实现

function partialUsingArguments(fn) {
   var args=Array.prototype.slice.call(arguments,1);
   function result(){
      return fn.apply(this,args.concat([].slice.call(arguments)));
   }    
   return result;
}

14、给定字符串 str,检查其是否包含连续重复的字母(a-zA-Z),包含返回 true,否则返回 false
function containsRepeatingLetter(str) {
  return  /([a-zA-Z])\1/.test(str)
}

解析:在正则表达式中,利用()进行分组,使用斜杠加数字表示引用,\1就是引用第一个分组,\2就是引用第二个分组。将[a-zA-Z]做为一个分组,然后引用,就可以判断是否有连续重复的字母。

\1的理解:子表达式的引用, 对正则表达式中前一个子表达式的引用,并不是指对子表达式模式的引用,而指的是与那个模式相匹配的文本的引用。

15、对象拷贝
  • 直接拷贝
    是对对象引用地址进行拷贝,并没有开辟新的栈,也就是拷贝后的结果是两个对象指向同一个引用地址,修改其中一个对象的属性,则另一个对象的属性也会改变。
var obj1={name="tom",sex="男"}
var obj2=obj1;
obj2.sex="女";
console.log(obj1.sex);    //女
JS问题总结_第1张图片
图片.png
  • 使用assign方法
var obj1={name="tom",sex="男"}
var obj2=Object.assign({},obj1);
obj2.sex="女";
console.log(obj1.sex);  //男
console.log(ob2.sex);  //女
JS问题总结_第2张图片
图片.png

Object.assign会课拷贝第一层的值,而到第二层的时候就是 复制引用。像这样的数组var arr=[{a:1,b:2},{a:3,b:4}]

  • 下面解决上面的问题,实现真正的拷贝
function clone(obj){
    if(!obj && typeof obj!=='object'){
        return ;
    }
    //来判断当前的obj是个对象还是数组
    var newObj=obj.constructor==Array?[]:{};

    for(var key in obj){
        if(obj[key]){
            if(typeof obj[key]=='object'){
                newObj[key]=obj[key].constructor==Array?[]:{};
                //递归
                newObj[key]=clone(obj[key]);
            }else{
                newObj[key]=obj[key];
            }

        }
    }
    return newObj;
}
var arr=[{a:1,b:2},{a:3,b:4}]
var newArr=clone(arr);
arr[0].a=3;
console.log(newArr[0].a);   //1
console.log(arr[0].a);   //3

16、使用push和apply合并数组

在使用push和apply合并数组之前,先使用ES6中的reduce方法进行合并数组。

var arr1=[1,2,3,4],arr2=[5,6,7,8,9];
arr1=arr2.reduce(function(coll,item){
    coll.push(item);
    return coll;
},arr1);

使用push和apply合并数组

var arr1=[1,2,3,4],arr2=[5,6,7,8,9];
arr1.push.apply(arr1,arr2);
17、toFixed 保留几位小数及取整

使用toFixed(n)方法后生成一个字符串类型的数据。

将一个字符串类型的数据转换成number类型的

var a=12.3456.toFixed(2) ;     // 2表示保留两位小数     '12.35'    string类型
a+=a ;      // 12.35     number类型

保留整数a=a|0 或者a=~~a

var a=123.456.toFixed(1);  //'123.45';
a=a|0;
console.log(a);   //123   number类型
var a=123.456.toFixed(1);  //'123.45';
a=~~a;
console.log(a);   //123   number类型
17、动态添加元素

比如向ul中添加10个li

    var ul=document.getElementById("ul-ele");
        //用createElement方法
    console.time('t1');    //计时开始
    for(var i=0;i<10;i++){
        var eleLi=document.createElement("li");
        eleLi.innerHTML=i;
        ul.appendChild(eleLi);
    }
     console.timeEnd('t1');   //计时结束  1.65ms

   //用innerHTML方式
     console.time('t2'); 
     var html='';
     for(var i=0;i<10;i++){
      html+="
  • "+i+"
  • "; } ul.innerHTML=html; console.timeEnd('t2'); ////计时结束 1.03ms

    可以看出第二种方法要比第一种方法节省时间,第一种方法要操作DOM十次。而第二种方法就大大减少了对DOM的操作次数。

    18、上一张++,下一张- - ,边界判断

    比如轮播图 切换下一张,上一张,可以用下面这种方法,就不用判断是否到第一张或者最后一张。

    //加加
    i=(i+1)%array.length 
    
    // 减减
    i=(i-1+array.length)%array.length
    

    未完待续。。。。。。。

    你可能感兴趣的:(JS问题总结)