js面试题

作者: lazy_tomato | 来源:发表于2020-05-10 21:16 被阅读0次

    1.简述同步和异步的区别

    • 同步:浏览器访问服务器请求,用户看得到页面刷新,重新发请求,等请求完,页面刷新,新内容出现,用户看到新内容,进行下一步操作
    • 异步:浏览器访问服务器请求,用户正常操作,浏览器后端进行请求。等请求完,页面不刷新,新内容也会出现,用户看到新内容

    2.怎么添加、移除、复制、创建、和查找节点

    (1)创建新节点

    createDocumentFragment() //创建一个DOM片段

    createElement() //创建一个具体的元素

    createTextNode() //创建一个文本节点

    (2)添加、移除、替换、插入

    appendChild()

    removeChild()

    replaceChild()

    insertBefore()

    (3)查找

    getElementsByTagName() //通过标签名称

    getElementsByName() //通过元素的Name属性的值

    getElementById() //通过元素Id,唯一性

    3.???实现一个函数clone 可以对 Javascript 中的五种主要数据类型(Number、string、Object、Array、Boolean)进行复制

    Object.prototype.clone=function(){
        var o= this.constructor===Array? [] : {};
        for(var e in this){
            o[e]= typeof this[e]==="object" ? this[e].clone() : this[e];
        }
        return o;
    };
    

    4.如何消除一个数组里面重复的元素

    function qc(arr1){
        let arr = [];
        for( let i = 0; i < arr1.length; i++) {
            if( arr.indexOf(arr1[i]) == -1) {
                arr.push(arr1[i])
            }
        }
        return arr;
    }
    arr1 = ["1","1","3","5","2","24","4","4","a","a","b"];
            
    console.log(qc(arr1));  
    

    个人总结:感觉就是,结合for循环遍历要处理的数组,用indexof,判断我返回的数组中有没有这个元素,没有就加进去。我在想印象中好像有更简单的方法

    5.***写一个返回闭包的函数

    闭包(closure)是Javascript语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现。闭包就是能够读取其他函数内部变量的函数。可以把闭包简单理解成”定义在一个函数内部的函数”。
    闭包有三个特性:
    1.函数嵌套函数;
    2.函数内部可以引用外部的参数和变量;
    3.参数和变量不会被垃圾回收机制回收。
    闭包就是一个函数的返回值为另外一个函数,在outer外部可以通过这个返回的函数访问outer内的局部变量.

    function outer(){
         var val = 0;
         return function (){
               val += 1;
               document.write(val + "<br />");
         };
    }
    var outObj = outer();
    outObj();//1,执行val += 1后,val还在
    outObj();//2
    outObj = null;//val 被回收
    var outObj1 = outer();
    outObj1();//1
    outObj1();//2
    

    ​ 闭包会使变量始终保存在内存中,如果不当使用会增大内存消耗(如果上例中定义很多outer(),则内存中会保存很多val变量)。

    javascript 的垃圾回收原理:

    (1)、在javascript中,如果一个对象不再被引用,那么这个对象就会被GC回收;

    (2)、如果两个对象互相引用,而不再被第3者所引用,那么这两个互相引用的对象也会被回收。

    那么使用闭包有什么好处呢?

    使用闭包的好处是:

    1.希望一个变量长期驻扎在内存中

    2.避免全局变量的污染

    3.私有成员的存在

    6.使用递归完成1到100的累加

    function sum(num) {
        if( num==1 ){
            return 1;
        }
        return num+sum(num-1);
    }
    console.log(sum(100))
    

    7. Javascript有哪几种数据类型

    基本数据类型:

    字符串 String
    数字 Number
    布尔 Boolean

    复合数据类型:

    数组 Array
    对象 Object

    特殊数据类型:

    Null 空对象
    Undefined 未定义

    判断js中的数据类型的几种方法

    判断js中的数据类型有一下几种方法:typeof、instanceof、 constructor、 prototype、 $.type()/jquery.type(),

    接下来主要比较一下这几种方法的异同。

    判断js中的数据类型的几种方法

    1、最常见的判断方法:typeof

    alert(typeof a)   ------------> string
    alert(typeof b)   ------------> number
    alert(typeof c)   ------------> object
    alert(typeof d)   ------------> object
    alert(typeof e)   ------------> function
    alert(typeof f)   ------------> function
    其中typeof返回的类型都是字符串形式,需注意,例如:
    alert(typeof a == "string") -------------> true
    alert(typeof a == String) ---------------> false
    另外typeof 可以判断function的类型;在判断除Object类型的对象时比较方便。
    

    2、判断已知对象类型的方法: instanceof

    alert(c instanceof Array) ---------------> true
    alert(d instanceof Date) 
    alert(f instanceof Function) ------------> true
    alert(f instanceof function) ------------> false
    注意:instanceof 后面一定要是对象类型,并且大小写不能错,该方法适合一些条件选择或分支。
    

    3、根据对象的constructor判断: constructor

    alert(c.constructor === Array) ----------> true
    alert(d.constructor === Date) -----------> true
    alert(e.constructor === Function) -------> true
    注意: constructor 在类继承时会出错
    eg:
          function A(){};
          function B(){};
          A.prototype = new B(); //A继承自B
          var aObj = new A();
          alert(aobj.constructor === B) -----------> true;
          alert(aobj.constructor === A) -----------> false;
    而instanceof方法不会出现该问题,对象直接继承和间接继承的都会报true:
          alert(aobj instanceof B) ----------------> true;
          alert(aobj instanceof B) ----------------> true;
    言归正传,解决construtor的问题通常是让对象的constructor手动指向自己:
          aobj.constructor = A; //将自己的类赋值给对象的constructor属性
          alert(aobj.constructor === A) -----------> true;
          alert(aobj.constructor === B) -----------> false; //基类不会报true了;
    

    4、通用但很繁琐的方法: prototype

    alert(Object.prototype.toString.call(a) === ‘[object String]’) -------> true;
    alert(Object.prototype.toString.call(b) === ‘[object Number]’) -------> true;
    alert(Object.prototype.toString.call(c) === ‘[object Array]’) -------> true;
    alert(Object.prototype.toString.call(d) === ‘[object Date]’) -------> true;
    alert(Object.prototype.toString.call(e) === ‘[object Function]’) -------> true;
    alert(Object.prototype.toString.call(f) === ‘[object Function]’) -------> true;
    大小写不能写错,比较麻烦,但胜在通用。
    

    5、万能的方法:jquery.type()

    如果对象是undefined或null,则返回相应的“undefined”或“null”。
    jQuery.type( undefined ) === "undefined"
    jQuery.type() === "undefined"
    jQuery.type( window.notDefined ) === "undefined"
    jQuery.type( null ) === "null"
    如果对象有一个内部的[[Class]]和一个浏览器的内置对象的 [[Class]] 相同,我们返回相应的 [[Class]] 名字。 (有关此技术的更多细节。 )
    jQuery.type( true ) === "boolean"
    jQuery.type( 3 ) === "number"
    jQuery.type( "test" ) === "string"
    jQuery.type( function(){} ) === "function"
    jQuery.type( [] ) === "array"
    jQuery.type( new Date() ) === "date"
    jQuery.type( new Error() ) === "error" // as of jQuery 1.9
    jQuery.type( /test/ ) === "regexp"
    其他一切都将返回它的类型“object”。
    

    9. console.log(1+'2')和console.log(1-'2')的打印结果

    console.log(1+'2') //12
    console.log(1-'2') //-1
    

    个人总结:第一眼看晕了,其实还好,+相当于字符串拼接,-号相当于运算符,会隐式的把 '2' 转换成 2

    10. Js的事件委托是什么,原理是什么

    事件委托就是利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。
    原理:事件委托是利用事件的冒泡原理来实现的,何为事件冒泡呢?就是事件从最深的节点开始,然后逐步向上传播事件。

    11.如何改变函数内部的this指针的指向

    1.bind:

    • fun.bind(thisArg[, arg1[, arg2[, …]]])
    • 他是直接改变这个函数的this指向并且返回一个新的函数,之后再次调用这个函数的时候this都是指向bind绑定的第一个参数。bind传参方式跟call方法一致。
    • thisArg 当绑定函数被调用时,该参数会作为原函数运行时的 this 指向。当使用new 操作符调用绑定函数时,该参数无效。
    • arg1, arg2, … 当绑定函数被调用时,这些参数将置于实参之前传递给被绑定的方法。

    2.call:

    • fun.call(thisArg, arg1, arg2, …)
    • call跟apply的用法几乎一样,唯一的不同就是传递的参数不同,call只能一个参数一个参数的传入。
    • thisArg: 在fun函数运行时指定的this值。需要注意的是,指定的this值并不一定是该函数执行时真正的this值,如果这个函数处于非严格模式下,则指定为null和undefined的this值会自动指向全局对象(浏览器中就是window对象),同时值为原始值(数字,字符串,布尔值)的this会指向该原始值的自动包装对象。
    • arg1, arg2, … 指定的参数列表

    3.apply

    • apply则只支持传入一个数组,哪怕是一个参数也要是数组形式。最终调用函数时候这个数组会拆成一个个参数分别传入。
    • thisArg 在 fun 函数运行时指定的 this 值。需要注意的是,指定的 this 值并不一定是该函数执行时真正的 this 值,如果这个函数处于非严格模式下,则指定为 null 或 undefined 时会自动指向全局对象(浏览器中就是window对象),同时值为原始值(数字,字符串,布尔值)的 this 会指向该原始值的自动包装对象。
    • argsArray 一个数组或者类数组对象,其中的数组元素将作为单独的参数传给 fun 函数。如果该参数的值为null 或 undefined,则表示不需要传入任何参数。从ECMAScript 5 开始可以使用类数组对象。

    四.总结

    • 当我们使用一个函数需要改变this指向的时候才会用到callapplybind
    • 如果你要传递的参数不多,则可以使用fn.call(thisObj, arg1, arg2 …)
    • 如果你要传递的参数很多,则可以用数组将参数整理好调用fn.apply(thisObj, [arg1, arg2 …])
      如果你想生成一个新的函数长期绑定某个函数给某个对象使用,则可以使用const newFn = fn.bind(thisObj); newFn(arg1, arg2…)
    • call和apply第一个参数为null/undefined,函数this指向全局对象,在浏览器中是window,在node中是global

    12.列举几种解决跨域问题的方式,且说明原理

    1.jsonp

    script标签是不受同源策略影响的,它可以引入来自任何地方的js文件。动态添加script

    2、使用window.name来进行跨域

    window对象有个name属性,该属性有个特征:即在一个窗口(window)的生命周期内,窗口载入的所有的页面都是共享一个window.name的,每个页面对window.name都有读写的权限,window.name是持久存在一个窗口载入过的所有页面中的,并不会因新页面的载入而进行重置。

    3、使用HTML5中新引进的window.postMessage方法来跨域传送数据

    window.postMessage(message,targetOrigin) 方法是html5新引进的特性,可以使用它来向其它的window对象发送消息,无论这个window对象是属于同源或不同源,目前IE8+、FireFox、Chrome、Opera等浏览器都已经支持window.postMessage方法。

    13.谈谈垃圾回收机制的方式及内存管理

    一、垃圾回收机制—GC

    Javascript具有自动垃圾回收机制(GC:Garbage Collecation),也就是说,执行环境会负责管理代码执行过程中使用的内存。

    原理:垃圾收集器会定期(周期性)找出那些不在继续使用的变量,然后释放其内存

    通常情况下有两种实现方式:标记清除和引用计数

    • 标记清除: js中最常用的垃圾回收方式就是标记清除。

    当变量进入环境时,例如,在函数中声明一个变量,就将这个变量标记为“进入环境”。从逻辑上讲,永远不能释放进入环境的变量所占用的内存,因为只要执行流进入相应的环境,就可能会用到它们。而当变量离开环境时,则将其标记为“离开环境”。

    • 引用计数的含义是跟踪记录每个值被引用的次数。

    当声明了一个变量并将一个引用类型值赋给该变量时,则这个值的引用次数就是1。如果同一个值又被赋给另一个变量,则该值的引用次数加1。相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值的引用次数减1。当这个值的引用次数变成0时,则说明没有办法再访问这个值了,因而就可以将其占用的内存空间回收回来。这样,当垃圾回收器下次再运行时,它就会释放那些引用次数为0的值所占用的内存。

    二、内存管理

    1)、Javascript引擎基础GC方案是(simple GC):mark and sweep(标记清除),即:

    • (1)遍历所有可访问的对象。
    • (2)回收已不可访问的对象。

    2)、GC的缺陷

    和其他语言一样,javascript的GC策略也无法避免一个问题:GC时,停止响应其他操作,这是为了安全考虑。而Javascript的GC在100ms甚至以上,对一般的应用还好,但对于JS游戏,动画对连贯性要求比较高的应用,就麻烦了。这就是新引擎需要优化的点:避免GC造成的长时间停止响应。
    3)、GC优化策略

    • 1)分代回收(Generation GC)
      这个和Java回收策略思想是一致的。目的是通过区分“临时”与“持久”对象;多回收“临时对象”区(young generation),少回收“持久对象”区(tenured generation),减少每次需遍历的对象,从而减少每次GC的耗时
    • 2)增量GC
      这个方案的思想很简单,就是“每次处理一点,下次再处理一点,如此类推”

    14.写一个function ,清除字符串前后的空格

    //重写trim方法
    if(!String.prototype.trim){
        String.prototype.trim = function(){
            return this.replace(/^\s+/,"").replace(/\s+$/,""); 
        }
    } 
    //写fntrim去掉左右空格
    function fntrim(str){
        return str.replace(/^\s+/,"").replace(/\s+$/,"");
    }
    

    15.js实现继承的方法有哪些

    JS继承的实现方式

    既然要实现继承,那么首先我们得有一个父类,代码如下:

    // 定义一个动物类
    function Animal (name) {
      // 属性
      this.name = name || 'Animal';
      // 实例方法
      this.sleep = function(){
        console.log(this.name + '正在睡觉!');
      }
    }
    // 原型方法
    Animal.prototype.eat = function(food) {
      console.log(this.name + '正在吃:' + food);
    };
    

    1、原型链继承

    核心:将父类的实例作为子类的原型

    function Cat(){ 
    }
    Cat.prototype = new Animal();
    Cat.prototype.name = 'cat';
    
    // Test Code
    var cat = new Cat();
    console.log(cat.name);
    console.log(cat.eat('fish'));
    console.log(cat.sleep());
    console.log(cat instanceof Animal); //true 
    console.log(cat instanceof Cat); //true
    

    特点:

    1. 非常纯粹的继承关系,实例是子类的实例,也是父类的实例
    2. 父类新增原型方法/原型属性,子类都能访问到
    3. 简单,易于实现

    缺点:

    1. 要想为子类新增属性和方法,必须要在new Animal()这样的语句之后执行,不能放到构造器中
    2. 无法实现多继承
    3. 来自原型对象的所有属性被所有实例共享(来自原型对象的引用属性是所有实例共享的)(详细请看附录代码:[示例1](javascript:void(0);))
    4. 创建子类实例时,无法向父类构造函数传参

    2、构造继承

    核心:使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类(没用到原型)

    function Cat(name){
      Animal.call(this);
      this.name = name || 'Tom';
    }
    
    // Test Code
    var cat = new Cat();
    console.log(cat.name);
    console.log(cat.sleep());
    console.log(cat instanceof Animal); // false
    console.log(cat instanceof Cat); // true
    

    特点:

    1. 解决了1中,子类实例共享父类引用属性的问题
    2. 创建子类实例时,可以向父类传递参数
    3. 可以实现多继承(call多个父类对象)

    缺点:

    1. 实例并不是父类的实例,只是子类的实例
    2. 只能继承父类的实例属性和方法,不能继承原型属性/方法
    3. 无法实现函数复用,每个子类都有父类实例函数的副本,影响性能

    3、实例继承

    核心:为父类实例添加新特性,作为子类实例返回

    function Cat(name){
      var instance = new Animal();
      instance.name = name || 'Tom';
      return instance;
    }
    
    // Test Code
    var cat = new Cat();
    console.log(cat.name);
    console.log(cat.sleep());
    console.log(cat instanceof Animal); // true
    console.log(cat instanceof Cat); // false
    

    特点:

    1. 不限制调用方式,不管是new 子类()还是子类(),返回的对象具有相同的效果

    缺点:

    1. 实例是父类的实例,不是子类的实例
    2. 不支持多继承

    4、拷贝继承

    function Cat(name){
      var animal = new Animal();
      for(var p in animal){
        Cat.prototype[p] = animal[p];
      }
      Cat.prototype.name = name || 'Tom';
    }
    
    // Test Code
    var cat = new Cat();
    console.log(cat.name);
    console.log(cat.sleep());
    console.log(cat instanceof Animal); // false
    console.log(cat instanceof Cat); // true
    

    特点:

    1. 支持多继承

    缺点:

    1. 效率较低,内存占用高(因为要拷贝父类的属性)
    2. 无法获取父类不可枚举的方法(不可枚举方法,不能使用for in 访问到)

    5、组合继承

    核心:通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用

    function Cat(name){
      Animal.call(this);
      this.name = name || 'Tom';
    }
    Cat.prototype = new Animal();
    Cat.prototype.constructor = Cat;
    // Test Code
    var cat = new Cat();
    console.log(cat.name);
    console.log(cat.sleep());
    console.log(cat instanceof Animal); // true
    console.log(cat instanceof Cat); // true
    

    特点:

    1. 弥补了方式2的缺陷,可以继承实例属性/方法,也可以继承原型属性/方法
    2. 既是子类的实例,也是父类的实例
    3. 不存在引用属性共享问题
    4. 可传参
    5. 函数可复用

    缺点:

    1. 调用了两次父类构造函数,生成了两份实例(子类实例将子类原型上的那份屏蔽了)

    6、寄生组合继承

    核心:通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性,避免的组合继承的缺点

    function Cat(name){
      Animal.call(this);
      this.name = name || 'Tom';
    }
    (function(){
      // 创建一个没有实例方法的类
      var Super = function(){};
      Super.prototype = Animal.prototype;
      //将实例作为子类的原型
      Cat.prototype = new Super();
    })();
    
    // Test Code
    var cat = new Cat();
    console.log(cat.name);
    console.log(cat.sleep());
    console.log(cat instanceof Animal); // true
    console.log(cat instanceof Cat); //true
    Cat.prototype.constructor = Cat; // 需要修复下构造函数
    

    特点:

    1. 堪称完美

    缺点:

    1. 实现较为复杂

    16.判断一个变量是否是数组,有哪些办法

    1、在ECMAScript5标准中Array类增加了一个静态方法isArray,我们可以直接用Array.isArray来判断变量是否是数组。
    Array.isArray([1,2,3]) //此处返回true

    2、但是某些比较老的浏览器,比如IE8及以下,没有实现Array的isArray方法,那么就需要换一种方式来判断:

    Object.prototype.toString.call([1,2,3]) //返回字符串:'[object Array]'

    那么我们定义一个函数来实现数组判断

            function isArray (value) {
    
                if  (Object.prototype.toString.call(value) === '[object Array]') {
    
                    return true
    
                }
    
                    return false
    
            }
    

    3、index of
    var arr=new Array(["b",2,"a",4,"test"]);

    17. let ,const ,var 有什么区别

    • let 和 const 定义的变量不会出现变量提升,而 var 定义的变量会提升。
    • let 和 const 是JS中的块级作用域
    • let 和 const 不允许重复声明(会抛出错误)
    • let 和 const 定义的变量在定义语句之前,如果使用会抛出错误(形成了暂时性死区),而 var 不会。
    • const 声明一个只读的常量。一旦声明,常量的值就不能改变(如果声明是一个对象,那么不能改变的是对象的引用地址)

    18.箭头函数与普通函数有什么区别

      1. 箭头函数是匿名函数,不能作为构造函数,不能使用new
      1. 箭头函数不绑定arguments,取而代之用rest参数...解决
      1. 箭头函数不绑定this,会捕获其所在的上下文的this值,作为自己的this值
      1. 箭头函数通过 call() 或 apply() 方法调用一个函数时,只传入了一个参数,对 this 并没有影响。
      1. 箭头函数没有原型属性
      1. 箭头函数不能当做Generator函数,不能使用yield关键字

    19.随机取1-10之间的整数

    Math.floor([Math.random]*10+1)

    20. new操作符具体干了什么

    • 创建一个空对象,并且this变量引用该对象,同时还继承了该函数的原型
    • 属性和方法被加入到this引用的对象中
    • 新创建的对象由this所引用,并且最后隐式的返回this

    个人总结:

    1. 在内存中申请了一个空闲空间,储存创建的新对象
    2. 把this设置为当前对象
    3. 设置对象的属性和方法的值
    4. 把this对象返回

    21. Ajax原理

    • Ajax的原理简单来说是在用户和服务器之间加了—个中间层(AJAX引擎),通过XmlHttpRequest对象来向服务器发异步请求,从服务器获得数据,然后用javascript来操作DOM而更新页面。使用户操作与服务器响应异步化。这其中最关键的一步就是从服务器获得请求数据
    • Ajax的过程只涉及JavaScriptXMLHttpRequestDOMXMLHttpRequestajax的核心机制
        //1. 创建连接
       var xhr = null;
       xhr = new XMLHttpRequest()
       // 2. 连接服务器
       xhr.open('get', url, true)
       // 3. 发送请求
       xhr.send(null);
       // 4. 接受请求
       xhr.onreadystatechange = function(){
           if(xhr.readyState == 4){
               if(xhr.status == 200){
                   success(xhr.responseText);
               } else { // fail
                   fail && fail(xhr.status);
               }
           }
       }
    

    22.模块化开发怎么做

    • 立即执行函数,不暴露私有成员
    var module1 = (function(){
        var _count = 0;
        var m1 = function(){
          //...
        };
        var m2 = function(){
          //...
        };
        return {
          m1 : m1,
          m2 : m2
        };
      })();
    

    23.异步加载Js的方式有哪些

    • defer,只支持IE
    • async
    • 创建script,插入到DOM中,加载完毕后callBack

    24. xml和 json的区别

    • 数据体积方面
      • JSON相对于XML来讲,数据的体积小,传递的速度更快些。
    • 数据交互方面
      • JSONJavaScript的交互更加方便,更容易解析处理,更好的数据交互
    • 数据描述方面
      • JSON对数据的描述性比XML较差
    • 传输速度方面
      • JSON的速度要远远快于XML

    25. webpack 如何实现打包的

    • WebPack是一个模块打包工具,你可以使用WebPack管理你的模块依赖,并编绎输出模块们所需的静态文件。它能够很好地管理、打包Web开发中所用到的HTMLJavascriptCSS以及各种静态文件(图片、字体等),让开发过程更加高效。对于不同类型的资源,webpack有对应的模块加载器。webpack模块打包器会分析模块间的依赖关系,最后 生成了优化且合并后的静态资源

    26.常见web安全及防护原理

    • sql注入原理
      • 就是通过把SQL命令插入到Web表单递交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令
    • 总的来说有以下几点
      • 永远不要信任用户的输入,要对用户的输入进行校验,可以通过正则表达式,或限制长度,对单引号和双"-"进行转换等
      • 永远不要使用动态拼装SQL,可以使用参数化的SQL或者直接使用存储过程进行数据查询存取
      • 永远不要使用管理员权限的数据库连接,为每个应用使用单独的权限有限的数据库连接
      • 不要把机密信息明文存放,请加密或者hash掉密码和敏感的信息

    XSS原理及防范

    • Xss(cross-site scripting)攻击指的是攻击者往Web页面里插入恶意html标签或者javascript代码。比如:攻击者在论坛中放一个看似安全的链接,骗取用户点击后,窃取cookie中的用户私密信息;或者攻击者在论坛中加一个恶意表单,当用户提交表单的时候,却把信息传送到攻击者的服务器中,而不是用户原本以为的信任站点

    XSS防范方法

    • 首先代码里对用户输入的地方和变量都需要仔细检查长度和对”<”,”>”,”;”,”’”等字符做过滤;其次任何内容写到页面之前都必须加以encode,避免不小心把html tag弄出来。这一个层面做好,至少可以堵住超过一半的XSS 攻击

    XSS与CSRF有什么区别吗?

    • XSS是获取信息,不需要提前知道其他用户页面的代码和数据包。CSRF是代替用户完成指定的动作,需要知道其他用户页面的代码和数据包。要完成一次CSRF攻击,受害者必须依次完成两个步骤
    • 登录受信任网站A,并在本地生成Cookie
    • 在不登出A的情况下,访问危险网站B

    CSRF的防御

    • 服务端的CSRF方式方法很多样,但总的思想都是一致的,就是在客户端页面增加伪随机数
    • 通过验证码的方法

    个人总结:

    说实话这么多默认的单词缩写,记不住,但是我印象比较深的是,在表单输入这一块,永远要进行处理,万一别人给你提交一段代码,在后台跑起来了,那就不是开玩笑的了。

    27.用过哪些设计模式

    • 工厂模式:
      • 工厂模式解决了重复实例化的问题,但还有一个问题,那就是识别问题,因为根本无法
      • 主要好处就是可以消除对象间的耦合,通过使用工程方法而不是new关键字
    • 构造函数模式
      • 使用构造函数的方法,即解决了重复实例化的问题,又解决了对象识别的问题,该模式与工厂模式的不同之处在于
      • 直接将属性和方法赋值给this对象;

    28.为什么要同源限制

    • 同源策略指的是:协议,域名,端口相同,同源策略是一种安全协议
    • 举例说明:比如一个黑客程序,他利用Iframe把真正的银行登录页面嵌到他的页面上,当你使用真实的用户名,密码登录时,他的页面就可以通过Javascript读取到你的表单中input中的内容,这样用户名,密码就轻松到手了。

    29.offsetWidth/offsetHeight,clientWidth/clientHeight与scrollWidth/scrollHeight的区别

    • offsetWidth/offsetHeight返回值包含content + padding + border,效果与e.getBoundingClientRect()相同
    • clientWidth/clientHeight返回值只包含content + padding,如果有滚动条,也不包含滚动条
    • scrollWidth/scrollHeight返回值包含content + padding + 溢出内容的尺寸

    30. javascript有哪些方法定义对象

    • 对象字面量:var obj = {};
    • 构造函数:var obj = new Object();
    • Object.create():var obj = Object.create(Object.prototype);

    31.说说你对promise的了解

    • 依照Promise/A+的定义,Promise有四种状态:
      • pending:初始状态, 非fulfilledrejected.
      • fulfilled:成功的操作.
      • rejected:失败的操作.
      • settled: Promise已被fulfilledrejected,且不是pending
    • 另外,fulfilledrejected一起合称settled
    • Promise对象用来进行延迟(deferred) 和异步(asynchronous) 计算

    Promise 的构造函数

    • 构造一个Promise,最基本的用法如下:
    var promise = new Promise(function(resolve, reject) {
            if (...) {  // succeed
                resolve(result);
            } else {   // fails
                reject(Error(errMessage));
            }
        });
    
    • Promise实例拥有then方法(具有then方法的对象,通常被称为thenable)。它的使用方法如下:

      1promise.then(onFulfilled, onRejected)

    • 接收两个函数作为参数,一个在fulfilled的时候被调用,一个在rejected的时候被调用,接收参数就是futureonFulfilled对应resolve,onRejected对应reject

    32.谈谈你对AMD、CMD的理解

    • CommonJS是服务器端模块的规范,Node.js采用了这个规范。CommonJS规范加载模块是同步的,也就是说,只有加载完成,才能执行后面的操作。AMD规范则是非同步加载模块,允许指定回调函数
    • AMD推荐的风格通过返回一个对象做为模块对象,CommonJS的风格通过对module.exportsexports的属性赋值来达到暴露模块对象的目的

    33. web开发中会话跟踪的方法有哪些

    • cookie
    • session
    • url重写
    • 隐藏input
    • ip地址

    34.介绍js有哪些内置对象?

    • ObjectJavaScript中所有对象的父对象
    • 数据封装类对象:ObjectArrayBooleanNumberString
    • 其他对象:FunctionArgumentsMathDateRegExpError

    35.说几条写JavaScript的基本规范?

    • 不要在同一行声明多个变量
    • 请使用===/!==来比较true/false或者数值
    • 使用对象字面量替代new Array这种形式
    • 不要使用全局函数
    • Switch语句必须带有default分支
    • If语句必须使用大括号
    • for-in循环中的变量 应该使用var关键字明确限定作用域,从而避免作用域污

    36.javascript创建对象的几种方式?

    javascript创建对象简单的说,无非就是使用内置对象或各种自定义对象,当然还可以用JSON;但写法有很多种,也能混合使用

    • 对象字面量的方式
    person={firstname:"Mark",lastname:"Yun",age:25,eyecolor:"black"};
    
    • function来模拟无参的构造函数
    function Person(){}
        var person=new Person();//定义一个function,如果使用new"实例化",该function可以看作是一个Class
            person.name="Mark";
            person.age="25";
            person.work=function(){
            alert(person.name+" hello...");
        }
    person.work();
    
    • function来模拟参构造函数来实现(用this关键字定义构造的上下文属性)
    function Pet(name,age,hobby){
           this.name=name;//this作用域:当前对象
           this.age=age;
           this.hobby=hobby;
           this.eat=function(){
              alert("我叫"+this.name+",我喜欢"+this.hobby+",是个程序员");
           }
        }
        var maidou =new Pet("麦兜",25,"coding");//实例化、创建对象
        maidou.eat();//调用eat方法
    
    • 用工厂方式来创建(内置对象)
    var wcDog =new Object();
         wcDog.name="旺财";
         wcDog.age=3;
         wcDog.work=function(){
           alert("我是"+wcDog.name+",汪汪汪......");
         }
         wcDog.work();
    
    • 用原型方式来创建
    function Dog(){
     }
     Dog.prototype.name="旺财";
     Dog.prototype.eat=function(){
     alert(this.name+"是个吃货");
     }
     var wangcai =new Dog();
     wangcai.eat();
    
    • 用混合方式来创建
    function Car(name,price){
         this.name=name;
         this.price=price; 
       }
        Car.prototype.sell=function(){
          alert("我是"+this.name+",我现在卖"+this.price+"万元");
         }
       var camry =new Car("凯美瑞",27);
       camry.sell(); 
    

    37. eval是做什么的?

    • 它的功能是把对应的字符串解析成JS代码并运行
    • 应该避免使用eval,不安全,非常耗性能(2次,一次解析成js语句,一次执行)
    • JSON字符串转换为JSON对象的时候可以用eval,var obj =eval('('+ str +')')

    39.[“1”, “2”, “3”].map(parseInt) 答案是多少?

    • [1, NaN, NaN]因为parseInt需要两个参数(val, radix),其中radix表示解析时用的基数。
    • map传了3(element, index, array),对应的radix不合法导致解析失败。

    40. javascript 代码中的”use strict”;是什么意思 ? 使用它区别是什么?

    • use strict是一种ECMAscript 5添加的(严格)运行模式,这种模式使得 Javascript 在更严格的条件下运行,使JS编码更加规范化的模式,消除Javascript语法的一些不合理、不严谨之处,减少一些怪异行为

    41. js延迟加载的方式有哪些?

    • deferasync、动态创建DOM方式(用得最多)、按需异步载入js

    42. defer 和 async

    • defer并行加载js文件,会按照页面上script标签的顺序执行
    • async并行加载js文件,下载完成立即执行,不会按照页面上script标签的顺序执行

    43.说说严格模式的限制

    • 变量必须声明后再使用
    • 函数的参数不能有同名属性,否则报错
    • 不能使用with语句
    • 禁止this指向全局对象

    44. attribute和property的区别是什么?

    • attributedom元素在文档中作为html标签拥有的属性;
    • property就是dom元素在js中作为对象拥有的属性。
    • 对于html的标准属性来说,attributeproperty是同步的,是会自动更新的
    • 但是对于自定义的属性来说,他们是不同步的

    45. ECMAScript6 怎么写class么,为什么会出现class这种东西?

    • 这个语法糖可以让有OOP基础的人更快上手js,至少是一个官方的实现了
    • 但对熟悉js的人来说,这个东西没啥大影响;一个Object.creat()搞定继承,比class简洁清晰的多

    46.常见兼容性问题

    • png24位的图片在iE6浏览器上出现背景,解决方案是做成PNG8
    • 浏览器默认的marginpadding不同。解决方案是加一个全局的*{margin:0;padding:0;}来统一,,但是全局效率很低,一般是如下这样解决:
    • IE下,event对象有x,y属性,但是没有pageX,pageY属性
    • Firefox下,event对象有pageX,pageY属性,但是没有x,y属性.

    47.函数防抖节流的原理

    防抖和节流的作用都是防止函数多次调用。区别在于,假设一个用户一直触发这个函数,且每次触发函数的间隔小于设置的时间,防抖的情况下只会调用一次,而节流的情况会每隔一定时间调用一次函数。

    防抖(debounce): n秒内函数只会执行一次,如果n秒内高频事件再次被触发,则重新计算时间

    function debounce(func, wait, immediate=true) {
        let timeout, context, args;
            // 延迟执行函数
            const later = () => setTimeout(() => {
                // 延迟函数执行完毕,清空定时器
                timeout = null
                // 延迟执行的情况下,函数会在延迟函数中执行
                // 使用到之前缓存的参数和上下文
                if (!immediate) {
                    func.apply(context, args);
                    context = args = null;
                }
            }, wait);
            let debounced = function (...params) {
                if (!timeout) {
                    timeout = later();
                    if (immediate) {
                        //立即执行
                        func.apply(this, params);
                    } else {
                        //闭包
                        context = this;
                        args = params;
                    }
                } else {
                    clearTimeout(timeout);
                    timeout = later();
                }
            }
        debounced.cancel = function () {
            clearTimeout(timeout);
            timeout = null;
        };
        return debounced;
    };
    

    防抖的应用场景:

    • 每次 resize/scroll 触发统计事件
    • 文本输入的验证(连续输入文字后发送 AJAX 请求进行验证,验证一次就好)

    节流(throttle): 高频事件在规定时间内只会执行一次,执行一次后,只有大于设定的执行周期后才会执行第二次。

    //underscore.js
    function throttle(func, wait, options) {
        var timeout, context, args, result;
        var previous = 0;
        if (!options) options = {};
    
        var later = function () {
            previous = options.leading === false ? 0 : Date.now() || new Date().getTime();
            timeout = null;
            result = func.apply(context, args);
            if (!timeout) context = args = null;
        };
    
        var throttled = function () {
            var now = Date.now() || new Date().getTime();
            if (!previous && options.leading === false) previous = now;
            var remaining = wait - (now - previous);
            context = this;
            args = arguments;
            if (remaining <= 0 || remaining > wait) {
                if (timeout) {
                    clearTimeout(timeout);
                    timeout = null;
                }
                previous = now;
                result = func.apply(context, args);
                if (!timeout) context = args = null;
            } else if (!timeout && options.trailing !== false) {
                // 判断是否设置了定时器和 trailing
                timeout = setTimeout(later, remaining);
            }
            return result;
        };
    
        throttled.cancel = function () {
            clearTimeout(timeout);
            previous = 0;
            timeout = context = args = null;
        };
    
        return throttled;
    };
    

    函数节流的应用场景有:

    • DOM 元素的拖拽功能实现(mousemove)
    • 射击游戏的 mousedown/keydown 事件(单位时间只能发射一颗子弹)
    • 计算鼠标移动的距离(mousemove)
    • Canvas 模拟画板功能(mousemove)
    • 搜索联想(keyup)
    • 监听滚动事件判断是否到页面底部自动加载更多:给 scroll 加了 debounce 后,只有用户停止滚动后,才会判断是否到了页面底部;如果是 throttle 的话,只要页面滚动就会间隔一段时间判断一次

    48.原始类型有哪几种?null是对象吗?

    .js中5种原始数据类型

    number:整数/小数/NaN
    string:
    boolean:
    null:
    undefined:

    Null类型是第二个只有一个值的数据类型,这个特殊的值是null,从逻辑角度来看,null值表示一个空对象指针,而这也正是使用typeof操作符检测null值会返回“object”的原因

    49.为什么console.log(0.2+0.1==0.3) //false

    JavaScript 中的 number 类型就是浮点型,JavaScript 中的浮点数采用IEEE-754 格式的规定,这是一种二进制表示法,可以精确地表示分数,比如 1/2,1/8,1/1024,每个浮点数占 64 位。但是,二进制浮点数表示法并不能精确的表示类似 0.1 这样 的简单的数字,会有舍入误差。
    由于采用二进制,JavaScript 也不能有限表示 1/10、1/2 等这样的分数。在二进制中,1/10(0.1)被表示为 0.00110011001100110011…… 注意 0011 是无限重复的,这是舍入误差造成的,所以对于 0.1 + 0.2 这样的运算,操作数会先被转成二进制,然后再计算

    50.说一下JS中类型转换的规则?

    转数字 转字符串 转布尔值
    undefined NaN “undefined” false
    null 0 “null” false
    true 1 “true”
    false 0 “false”
    0 “0” false
    -0 “0” false
    NaN “NaN” false
    Infinity “Infinity” true
    -Infinity ”-Infinity” true
    1(非零) “1” true
    {}(任意对象) 见下文 见下文 true
    0 ”” true
    9 9 “9” true
    ”a” NaN 使用.join()方法 true
    function(){}(任意函数) NaN 见下文 true

    Number的原始类型转换规则

    • 数值转换后还是数值
    • 字符串如果可以解析为数值则为数值, 空字符串为0, 无法解析的字符串为NaN
    • 布尔转数值, true转为1, false转为0
    • null转换为0
    img

    原始类型转换Number

    Number的对象类型转换规则

    传入实例M, 先调用M的valueOf(), 如果返回值V为基本数据类型, 则直接使用Number(V), 求最终返回值
    如果T不属于基本数据类型, 则调用M的toString(), 如果返回值S为基本数据类型, 则直接使用Number(S),求最后的结果, 如果S不属于基本数据类型, 则直接返回NaN

    img

    对象类型转换1

    img

    对象类型转换2

    String的原始类型转换规则

    • 数值(Number)转为相应的字符串
    • 字符串(String) 转换后还是字符串
    • 布尔值(Boolean)转换规则: true => 'true', false=> 'false'
    • undefine 转换为"undefine"
    • null 转换为'null'
    img

    String原始类型转换

    String 的对象类型转换规则

    与Number的对象转换规则类似, 区别是: 先调用对象的toString(), 然后再调用valueOf()
    其实正常情况下, 对象调用自身的toString()后, 对象就可以转换为string基本类型, valueOf() 没有机会被调用, 但万事有个例, 如果我们重新定义了对象的toString()方法,使其返回非基本类型的值, 那样就有机会调用对象的valueOf()方法了

    img

    String对象类型转换规则

    Boolean的原始类型转换 和 对象类型转换

    undefined,null,NaN,'',-0,+0皆为false, 其余为true

    隐式类型转换

    四则运算+, -, *, /

    img

    隐式类型转换之四则运算

    51.深拷贝和浅拷贝的区别?如何实现

    浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。浅拷贝只复制对象的第一层属性
    深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。对对象的属性进行递归复制

    浅拷贝 实现方法

    1、可以通过简单的赋值实现

    类似上面的例子,当然,我们也可以封装一个简单的函数,如下:

     function simpleClone(initalObj) {    
          var obj = {};    
          for ( var i in initalObj) {
            obj[i] = initalObj[i];
          }    
          return obj;
        }
    
        var obj = {
          a: "hello",
          b:{
              a: "world",
              b: 21
            },
          c:["Bob", "Tom", "Jenny"],
          d:function() {
              alert("hello world");
            }
        }
        var cloneObj = simpleClone(obj); 
        console.log(cloneObj.b); 
        console.log(cloneObj.c);
        console.log(cloneObj.d);
    
        cloneObj.b.a = "changed";
        cloneObj.c = [1, 2, 3];
        cloneObj.d = function() { alert("changed"); };
        console.log(obj.b);
        console.log(obj.c);
        console.log(obj.d);
    

    2、Object.assign()实现

    Object.assign() 方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。但是 Object.assign() 进行的是浅拷贝,拷贝的是对象的属性的引用,而不是对象本身。

    var obj = { a: {a: "hello", b: 21} };
    
    var initalObj = Object.assign({}, obj);
    
    initalObj.a.a = "changed";
    
    console.log(obj.a.a); //  "changed"
    

    注意:当object只有一层的时候,是深拷贝,例如如下:

    var obj1 = { a: 10, b: 20, c: 30 };
    var obj2 = Object.assign({}, obj1);
    obj2.b = 100;
    console.log(obj1);
    // { a: 10, b: 20, c: 30 } <-- 沒被改到
    console.log(obj2);
    // { a: 10, b: 100, c: 30 }
    

    深拷贝的实现方式

    1、对象只有一层的话可以使用上面的:Object.assign()函数

    2、转成 JSON 再转回来

    var obj1 = { body: { a: 10 } };
    var obj2 = JSON.parse(JSON.stringify(obj1));
    obj2.body.a = 20;
    console.log(obj1);
    // { body: { a: 10 } } <-- 沒被改到
    console.log(obj2);
    // { body: { a: 20 } }
    console.log(obj1 === obj2);
    // false
    console.log(obj1.body === obj2.body);
    // false
    

    用JSON.stringify把对象转成字符串,再用JSON.parse把字符串转成新的对象。

    可以封装如下函数

    var cloneObj = function(obj){
        var str, newobj = obj.constructor === Array ? [] : {};
        if(typeof obj !== 'object'){
            return;
        } else if(window.JSON){
            str = JSON.stringify(obj), //系列化对象
            newobj = JSON.parse(str); //还原
        } else {
            for(var i in obj){
                newobj[i] = typeof obj[i] === 'object' ? 
                cloneObj(obj[i]) : obj[i]; 
            }
        }
        return newobj;
    };
    

    3、递归拷贝

    function deepClone(initalObj, finalObj) {    
      var obj = finalObj || {};    
      for (var i in initalObj) {        
        var prop = initalObj[i];        // 避免相互引用对象导致死循环,如initalObj.a = initalObj的情况
        if(prop === obj) {            
          continue;
        }        
        if (typeof prop === 'object') {
          obj[i] = (prop.constructor === Array) ? [] : {};            
          arguments.callee(prop, obj[i]);
        } else {
          obj[i] = prop;
        }
      }    
      return obj;
    }
    var str = {};
    var obj = { a: {a: "hello", b: 21} };
    deepClone(obj, str);
    console.log(str.a);
    

    4、使用Object.create()方法

    直接使用var newObj = Object.create(oldObj),可以达到深拷贝的效果。

    function deepClone(initalObj, finalObj) {    
      var obj = finalObj || {};    
      for (var i in initalObj) {        
        var prop = initalObj[i];        // 避免相互引用对象导致死循环,如initalObj.a = initalObj的情况
        if(prop === obj) {            
          continue;
        }        
        if (typeof prop === 'object') {
          obj[i] = (prop.constructor === Array) ? [] : Object.create(prop);
        } else {
          obj[i] = prop;
        }
      }    
      return obj;
    }
    

    5、jquery

    jquery 有提供一个$.extend可以用来做 Deep Copy。

    var $ = require('jquery');
    var obj1 = {
        a: 1,
        b: { f: { g: 1 } },
        c: [1, 2, 3]
    };
    var obj2 = $.extend(true, {}, obj1);
    console.log(obj1.b.f === obj2.b.f);
    // false
    

    6、lodash

    另外一个很热门的函数库lodash,也有提供_.cloneDeep用来做 Deep Copy。

    var _ = require('lodash');
    var obj1 = {
        a: 1,
        b: { f: { g: 1 } },
        c: [1, 2, 3]
    };
    var obj2 = _.cloneDeep(obj1);
    console.log(obj1.b.f === obj2.b.f);
    // false
    

    这个性能还不错,使用起来也很简单。

    52.如何判断this?箭头函数的this是什么

    1.谁作为拥有者调用它就指向谁

    function a() { 
        console.log(this); 
    }
    var b  = {};
    b.hehe = a;
    b.hehe();
    //这时候this指向b//常见的就是绑定事件
    
    1. bind谁就指向谁
    function a() { 
        console.log(this); 
    }
    var b  = {};
    var c = {};
    b.hehe = a.bind(c);
    b.hehe();
    //这时候this指向c//如果你用bind的话
    

    3.没有拥有者,直接调用,就指向window

    function a() { 
        console.log(this); 
    }
    a();
    //this指向window
    

    4.call谁就是谁,apply谁就是谁,其实bind就是通过call和apply实现的


    箭头函数不绑定this,会捕获其所在的上下文的this值,作为自己的this值

    53.== 和 ===的区别

    1.===:三个等号我们称为等同符,当等号两边的值为相同类型的时候,直接比较等号两边的值,值相同则返回true,若等号两边的值类型不同时直接返回false。

    例:100===“100” //返回false

    abc===“abc” //返回false

    ‘abc’===“abc” //返回true

    NaN===NaN //返回false

    false===false //返回true

    2.==:两个等号我们称为等值符,当等号两边的值为相同类型时比较值是否相同,类型不同时会发生类型的自动转换,转换为相同的类型后再作比较。

    54.什么是闭包

    • 闭包就是能够读取其他函数内部变量的函数
    • 闭包是指有权访问另一个函数作用域中变量的函数,创建闭包的最常见的方式就是在一个函数内创建另一个函数,通过另一个函数访问这个函数的局部变量,利用闭包可以突破作用链域
    • 闭包的特性:
      • 函数内再嵌套函数
      • 内部函数可以引用外层的参数和变量
      • 参数和变量不会被垃圾回收机制回收

    说说你对闭包的理解

    • 使用闭包主要是为了设计私有的方法和变量。闭包的优点是可以避免全局变量的污染,缺点是闭包会常驻内存,会增大内存使用量,使用不当很容易造成内存泄露。在js中,函数即闭包,只有函数才会产生作用域的概念
    • 闭包 的最大用处有两个,一个是可以读取函数内部的变量,另一个就是让这些变量始终保持在内存中
    • 闭包的另一个用处,是封装对象的私有属性和私有方法
    • 好处:能够实现封装和缓存等;
    • 坏处:就是消耗内存、不正当使用会造成内存溢出的问题

    使用闭包的注意点

    • 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露
    • 解决方法是,在退出函数之前,将不使用的局部变量全部删除

    55. JavaScript原型,原型链 ? 有什么特点?

    • 每个对象都会在其内部初始化一个属性,就是prototype(原型),当我们访问一个对象的属性时
    • 如果这个对象内部不存在这个属性,那么他就会去prototype里找这个属性,这个prototype又会有自己的prototype,于是就这样一直找下去,也就是我们平时所说的原型链的概念
    • 关系:instance.constructor.prototype = instance.__proto__
    • 特点:
      • JavaScript对象是通过引用来传递的,我们创建的每个新对象实体中并没有一份属于自己的原型副本。当我们修改原型时,与之相关的对象也会继承这一改变
    • 当我们需要一个属性的时,Javascript引擎会先看当前对象中是否有这个属性, 如果没有的
    • 就会查找他的Prototype对象是否有这个属性,如此递推下去,一直检索到Object内建对象

    56. typeof()和instanceof()的用法区别

    typeof() 是一个一元运算,放在一个运算数之前,运算数可以是任意类型。
    它返回值是一个字符串,该字符串说明运算数的类型。
    ** instanceof() ** 运算符用来判断一个构造函数的prototype属性所指向的对象是否存在另外一个要检测对象的原型链上。通常来讲,使用 instanceof 就是判断一个实例是否属于某种类型。

    57.什么是变量提升

    变量提升:函数声明和变量声明总是会被解释器悄悄地被"提升"到方法体的最顶部。

    JavaScript 只有声明的变量会提升,初始化的不会。

    var x =5;// 初始化 x  
    var y =7;// 初始化 y  
      
    elem = document.getElementById("demo");// 查找元素  
    elem.innerHTML = x +" "\+ y;  // 5 7
    varx =5;// 初始化 x  
      
    elem = document.getElementById("demo");// 查找元素  
    elem.innerHTML = x +" "\+ y;           // 显示 x 和 y  
      
    vary =7;// 初始化 y
    
    输出结果:x 为:5,y 为:undefined
    

    y 输出了undefined,这是因为变量声明 (var y) 提升了,但是初始化(y = 7) 并不会提升,所以 y 变量是一个未定义的变量。
    类似下列代码

    var x = 5; // 初始化 x
    var y;     // 声明 y
    
    elem = document.getElementById("demo"); // 查找元素
    elem.innerHTML = x + " " + y;           // 显示 x 和 y
    
    y = 7;    // 设置 y 为 7
    

    1.函数声明会被提升 对于函数表达式,是不会提升的

    1         //函数声明, 形如:
    2         function show(){
    3             console.log( '函数声明方式' );
    4         }
    5 
    6         //函数表达式, 形如:
    7         var show = function(){
    8             console.log( '表达式方式' );
    9         }
    

    2.出现同名的函数声明,变量声明的时候, 函数声明会被优先提升,变量声明会被忽略

            show(); //你好
            var show;
            function show(){
                console.log( '你好' );
            }
            show = function(){
                console.log( 'hello' );
            }
    

    上面这段代码,结果为什么会是 '你好'?
    当出现同名的函数声明,变量声明的时候, 函数声明会被优先提升,变量声明会被忽略。 所以经过编译之后,就变成:

            function show(){
                console.log( '你好' );
            }
            show(); //你好
            show = function(){
                console.log( 'hello' );
            }
            show();//如果这里在调用一次,就是hello, 因为show函数体在执行阶段 被 重新赋值了
    

    3.如果有同名的函数声明,后面的会覆盖前面的

            show(); //how are you
            var show;
            function show(){
                console.log( 'hello' );
            }    
            show = function(){
                console.log( '你好' );
            }
            function show(){
                console.log( 'how are you!' );
            }
    //上面的代码经过编译之后,变成如下形式:
            function show(){
                console.log( 'how are you!' );
            }
            show(); //how are you
            show = function(){
                console.log( '你好' );
            }
            show(); //如果在这里再执行一次,结果:你好
    

    58.all、apply以及bind函数内部实现是怎么样的

    call, apply, bind都是改变函数执行的上下文,说的直白点就是改变了函数this的指向。不同的是:call和apply改变了函数的this,并且执行了该函数,而bind是改变了函数的this,并返回一个函数,但不执行该函数。

    看下面的例子1:

    var doThu = function(a, b) {
        console.log(this)
        console.log(this.name)
        console.log([a, b])
    }
    var stu = {
        name: 'xiaoming',
        doThu: doThu,
    }
    stu.doThu(1, 2) // stu对象 xiaoming [1, 2]
    doThu.call(stu, 1, 2) // stu对象 xiaoming [1, 2]
    

    由此可见,在stu上添加一个属性doThu,再执行这个函数,就将doThu的this指向了stu。而call的作用就与此相当,只不过call为stu添加了doThu方法后,执行了doThu,然后再将doThu这个方法从stu中删除。

    下面来看call函数的内部实现原理:

    Function.prototype.call = function(thisArg, args) {
        // this指向调用call的对象
        if (typeof this !== 'function') { // 调用call的若不是函数则报错
            throw new TypeError('Error')
        }
        thisArg = thisArg || window
        thisArg.fn = this   // 将调用call函数的对象添加到thisArg的属性中
        const result = thisArg.fn(...[...arguments].slice(1)) // 执行该属性
        delete thisArg.fn   // 删除该属性
        return result
    }
    

    apply的实现原理和call一样,只不过是传入的参数不同而已。下面只给出代码,不做解释:

    Function.prototype.apply = function(thisArg, args) {
        if (typeof this !== 'function') { 
            throw new TypeError('Error')
        }
        thisArg = thisArg || window
        thisArg.fn = this
        let result
        if(args) {
            result = thisArg.fn(...args)
        } else {
            result = thisArg.fn()
        }
        delete thisArg.fn
        return result
    }
    

    bind的实现原理比call和apply要复杂一些,bind中需要考虑一些复杂的边界条件。bind后的函数会返回一个函数,而这个函数也可能被用来实例化:

    Function.prototype.bind = function(thisArg) {
        if(typeof this !== 'function'){
            throw new TypeError(this + 'must be a function');
        }
        // 存储函数本身
        const _this  = this;
        // 去除thisArg的其他参数 转成数组
        const args = [...arguments].slice(1)
        // 返回一个函数
        const bound = function() {
            // 可能返回了一个构造函数,我们可以 new F(),所以需要判断
            if (this instanceof bound) {
                return new _this(...args, ...arguments)
            }
            // apply修改this指向,把两个函数的参数合并传给thisArg函数,并执行thisArg函数,返回执行结果
            return _this.apply(thisArg, args.concat(...arguments))
        }
        return bound
    }
    

    59.为什么会出现setTimeout倒计时误差?如何减少

    <!DOCTYPE html>
    <html>
        <head>
            <meta charset="utf-8">
            <title></title>
        </head>
        <body>
            <p id="message"></p>
        </body>
        <script type="text/javascript">
            var message = document.getElementById("message");
            var count = 1000;
            function animate() {
                var start = +new Date();
                message.innerHTML = count--;
                var finish = +new Date();
                setTimeout(animate, 1000 - (finish-start));
                
            }
            animate();
        </script>
    </html>
    

    本例实现了倒数1000秒的功能,我们在使用setTimeout的时候如果要一个函数每每隔1秒执行一次就会这样写

    setTimeout(animate, 1000);
    但是这样会忽略animate方法本身的运行时间,所以我们可以在执行animate方法的时候计算这个方法主要的语句的执行时间,之后在setTimeout中减去那个由于运行语句而耽搁的时间,从而实现更加精确的计时

    60.谈谈你对JS执行上下文栈和作用域链的理解

    执行上下文就是当前 JavaScript 代码被解析和执行时所在环境, JS执行上下文栈可以认为是一个存储函数调用的栈结构,遵循先进后出的原则。

    • JavaScript执行在单线程上,所有的代码都是排队执行。
    • 一开始浏览器执行全局的代码时,首先创建全局的执行上下文,压入执行栈的顶部。
    • 每当进入一个函数的执行就会创建函数的执行上下文,并且把它压入执行栈的顶部。当前函数执行-完成后,当前函数的执行上下文出栈,并等待垃圾回收。
    • 浏览器的JS执行引擎总是访问栈顶的执行上下文。
    • 全局上下文只有唯一的一个,它在浏览器关闭时出栈。

    作用域链: 无论是 LHS 还是 RHS 查询,都会在当前的作用域开始查找,如果没有找到,就会向上级作用域继续查找目标标识符,每次上升一个作用域,一直到全局作用域为止。

    61.new的原理是什么?通过new的方式创建对象和通过字面量创建有什么区别?

    new:

    1. 创建一个新对象。
    2. 这个新对象会被执行[[原型]]连接。
    3. 将构造函数的作用域赋值给新对象,即this指向这个新对象.
    4. 如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象。
    function new(func) {
        lat target = {};
        target.__proto__ = func.prototype;
        let res = func.call(target);
        if (typeof(res) == "object" || typeof(res) == "function") {
            return res;
        }
        return target;
    }
    

    字面量创建对象,不会调用 Object构造函数, 简洁且性能更好;

    new Object() 方式创建对象本质上是方法调用,涉及到在proto链中遍历该方法,当找到该方法后,又会生产方法调用必须的 堆栈信息,方法调用结束后,还要释放该堆栈,性能不如字面量的方式。

    通过对象字面量定义对象时,不会调用Object构造函数。

    62. prototype 和 proto 区别是什么?

    prototype是构造函数的属性。

    __proto__是每个实例都有的属性,可以访问 [[prototype]] 属性。

    实例的__proto__与其构造函数的prototype指向的是同一个对象。

    function Student(name) {
        this.name = name;
    }
    Student.prototype.setAge = function(){
        this.age=20;
    }
    let Jack = new Student('jack');
    console.log(Jack.__proto__);
    //console.log(Object.getPrototypeOf(Jack));;
    console.log(Student.prototype);
    console.log(Jack.__proto__ === Student.prototype);//true
    

    63.使用ES5实现一个继承?

    组合继承(最常用的继承方式)

    function SuperType() {
        this.name = name;
        this.colors = ['red', 'blue', 'green'];
    }
    SuperType.prototype.sayName = function() {
        console.log(this.name);
    }
    
    function SubType(name, age) {
        SuperType.call(this, name);
        this.age = age;
    }
    SubType.prototype = new SuperType();
    SubType.prototype.constructor = SubType;
    
    SubType.prototype.sayAge = function() {
        console.log(this.age);
    }
    

    64.取数组的最大值(ES5、ES6)

    // ES5 的写法
    Math.max.apply(null, [14, 3, 77, 30]);
    
    // ES6 的写法
    Math.max(...[14, 3, 77, 30]);
    
    // reduce
    [14,3,77,30].reduce((accumulator, currentValue)=>{
        return accumulator = accumulator > currentValue ? accumulator : currentValue
    });
    

    65. ES6 新的特性有哪些?

    1. 新增了块级作用域(let,const)
    2. 提供了定义类的语法糖(class)
    3. 新增了一种基本数据类型(Symbol)
    4. 新增了变量的解构赋值
    5. 函数参数允许设置默认值,引入了rest参数,新增了箭头函数
    6. 数组新增了一些API,如 isArray / from / of 方法;数组实例新增了 entries(),keys() 和 values() 等方法
    7. 对象和数组新增了扩展运算符
    8. ES6 新增了模块化(import/export)
    9. ES6 新增了 Set 和 Map 数据结构
    10. ES6 原生提供 Proxy 构造函数,用来生成 Proxy 实例
    11. ES6 新增了生成器(Generator)和遍历器(Iterator)

    66. promise 有几种状态, Promise 有什么优缺点 ?

    promise有三种状态: fulfilled, rejected, pending.

    Promise 的优点:

    1. 一旦状态改变,就不会再变,任何时候都可以得到这个结果
    2. 可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数

    Promise 的缺点:

    1. 无法取消 Promise
    2. 当处于pending状态时,无法得知目前进展到哪一个阶段

    67. Promise构造函数是同步还是异步执行,then呢 ?promise如何实现then处理 ?

    Promise的构造函数是同步执行的。then 是异步执行的。

    68. Promise和 setTimeout的区别 ?

    Promise 是微任务,setTimeout 是宏任务,同一个事件循环中,promise.then总是先于 setTimeout 执行。

    69.如何实现 Promise.all ?

    要实现 Promise.all,首先我们需要知道 Promise.all 的功能:

    1. 如果传入的参数是一个空的可迭代对象,那么此promise对象回调完成(resolve),只有此情况,是同步执行的,其它都是异步返回的。
    2. 如果传入的参数不包含任何 promise,则返回一个异步完成.

    promises 中所有的promise都“完成”时或参数中不包含 promise 时回调完成。

    1. 如果参数中有一个promise失败,那么Promise.all返回的promise对象失败
    2. 在任何情况下,Promise.all 返回的 promise 的完成状态的结果都是一个数组
    Promise.all = function (promises) {
        return new Promise((resolve, reject) => {
            let index = 0;
            let result = [];
            if (promises.length === 0) {
                resolve(result);
            } else {
                setTimeout(() => {
                    function processValue(i, data) {
                        result[i] = data;
                        if (++index === promises.length) {
                            resolve(result);
                        }
                    }
                    for (let i = 0; i < promises.length; i++) {
                        //promises[i] 可能是普通值
                        Promise.resolve(promises[i]).then((data) => {
                            processValue(i, data);
                        }, (err) => {
                            reject(err);
                            return;
                        });
                    }
                })
            }
        });
    }
    

    70.如何实现 Promise.finally ?

    不管成功还是失败,都会走到finally中,并且finally之后,还可以继续then。并且会将值原封不动的传递给后面的then.

    Promise.prototype.finally = function (callback) {
        return this.then((value) => {
            return Promise.resolve(callback()).then(() => {
                return value;
            });
        }, (err) => {
            return Promise.resolve(callback()).then(() => {
                throw err;
            });
        });
    }
    

    71.如何判断img加载完成

    一、load事件
    
    
    <!DOCTYPE HTML>
    
    <html> 
    
    <head> 
    
        <meta charset="utf-8">
    
        <title>img - load event</title>
    
    </head> 
    
    <body>
    
        <img id="img1" src="http://pic1.win4000.com/wallpaper/f/51c3bb99a21ea.jpg">
    
        <p id="p1">loading...</p>
    
        <script type="text/javascript">
    
            img1.onload = function() {
    
                p1.innerHTML = 'loaded'
    
            }
    
        </script>
    
    </body>
    
    </html> 
    

    测试,所有浏览器都显示出了“loaded”,说明所有浏览器都支持img的load事件。

    二、readystatechange事件

    <!DOCTYPE HTML>
    
    <html> 
    
    <head> 
    
        <meta charset="utf-8">
    
        <title>img - readystatechange event</title>
    
    </head> 
    
    <body>
    
        <img id="img1" src="http://pic1.win4000.com/wallpaper/f/51c3bb99a21ea.jpg">
    
        <p id="p1">loading...</p>
    
        <script type="text/javascript">
    
            img1.onreadystatechange = function() {
    
                if(img1.readyState=="complete"||img1.readyState=="loaded"){ 
    
                    p1.innerHTML = 'readystatechange:loaded'
    
                }
    
            }
    
        </script>
    
    </body>
    
    </html> 
    

    readyState为complete和loaded则表明图片已经加载完毕。测试IE6-IE10支持该事件,其它浏览器不支持。

    三、img的complete属性

    <!DOCTYPE HTML>
    
    <html> 
    
    <head> 
    
        <meta charset="utf-8">
    
        <title>img - complete attribute</title>
    
    </head> 
    
    <body>
    
        <img id="img1" src="http://pic1.win4000.com/wallpaper/f/51c3bb99a21ea.jpg">
    
        <p id="p1">loading...</p>
    
        <script type="text/javascript">
    
            function imgLoad(img, callback) {
    
                var timer = setInterval(function() {
    
                    if (img.complete) {
    
                        callback(img)
    
                        clearInterval(timer)
    
                    }
    
                }, 50)
    
            }
    
            imgLoad(img1, function() {
    
                p1.innerHTML('加载完毕')
    
            })
    
        </script>
    
    </body>
    
    </html> 
    

    轮询不断监测img的complete属性,如果为true则表明图片已经加载完毕,停止轮询。该属性所有浏览器都支持。

    72.如何阻止冒泡?

    冒泡型事件:事件按照从最特定的事件目标到最不特定的事件目标(document对象)的顺序触发。

    w3c的方法是e.stopPropagation(),IE则是使用e.cancelBubble = true。

    //阻止冒泡行为 
    function stopBubble(e) { 
    //如果提供了事件对象,则这是一个非IE浏览器 
    if ( e && e.stopPropagation ) 
        //因此它支持W3C的stopPropagation()方法 
        e.stopPropagation(); 
    else 
        //否则,我们需要使用IE的方式来取消事件冒泡 
        window.event.cancelBubble = true; 
    }
    

    73.如何阻止默认事件?

    w3c的方法是e.preventDefault(),IE则是使用e.returnValue = false

    //阻止浏览器的默认行为 
    function stopDefault( e ) { 
        //阻止默认浏览器动作(W3C) 
        if ( e && e.preventDefault ) 
            e.preventDefault(); 
        //IE中阻止函数器默认动作的方式 
        else 
            window.event.returnValue = false; 
        return false; 
    }
    

    74. ajax 请求时,如何解释 json 数据

    • 使用eval parse,但是鉴于安全性考虑 使用parse更靠谱;

    75.json和jsonp的区别?

    json返回的是一串json格式数据;而jsonp返回的是脚本代码(包含一个函数调用)

    jsonp的全名叫做json with padding,就是把json对象用符合js语法的形式包裹起来以使其他的网站可以请求到,也就是将json封装成js文件传过去。

    76.如何用原生js给一个按钮绑定两个onclick事件?

    Var  btn=document.getElementById(‘btn’);
    //事件监听 绑定多个事件
    var btn4 = document.getElementById("btn4");
    btn4.addEventListener("click",hello1);
    btn4.addEventListener("click",hello2);
    function hello1(){
     alert("hello 1");
    }
    function hello2(){
     alert("hello 2");
    }
    

    77.拖拽会用到哪些事件

    · dragstart:拖拽开始时在被拖拽元素上触发此事件,监听器需要设置拖拽所需数据,从操作系统拖拽文件到浏览器时不触发此事件.

    · dragenter:拖拽鼠标进入元素时在该元素上触发,用于给拖放元素设置视觉反馈,如高亮

    · dragover:拖拽时鼠标在目标元素上移动时触发.监听器通过阻止浏览器默认行为设置元素为可拖放元素.

    · dragleave:拖拽时鼠标移出目标元素时在目标元素上触发.此时监听器可以取消掉前面设置的视觉效果.

    · drag:拖拽期间在被拖拽元素上连续触发

    · drop:鼠标在拖放目标上释放时,在拖放目标上触发.此时监听器需要收集数据并且执行所需操作.如果是从操作系统拖放文件到浏览器,需要取消浏览器默认行为.

    · dragend:鼠标在拖放目标上释放时,在拖拽元素上触发.将元素从浏览器拖放到操作系统时不会触发此事件.

    78. document.write和 innerHTML的区别

    document.write是直接写入到页面的内容流,如果在写之前没有调用document.open, 浏览器会自动调用open。每次写完关闭之后重新调用该函数,会导致页面被重写。

    innerHTML则是DOM页面元素的一个属性,代表该元素的html内容。你可以精确到某一个具体的元素来进行更改。如果想修改document的内容,则需要修改document.documentElement.innerElement。

    innerHTML将内容写入某个DOM节点,不会导致页面全部重绘

    innerHTML很多情况下都优于document.write,其原因在于其允许更精确的控制要刷新页面的那一个部分。

    79.jQuery的事件委托方法bind 、live、delegate、on之间有什么区别?

    (1)、bind 【jQuery 1.3之前】

    定义和用法:主要用于给选择到的元素上绑定特定事件类型的监听函数;

    语法:bind(type,[data],function(eventObject));

    特点:

    (1)、适用于页面元素静态绑定。只能给调用它的时候已经存在的元素绑定事件,不能给未来新增的元素绑定事件。

    (2)、当页面加载完的时候,你才可以进行bind(),所以可能产生效率问题。

    实例如下:$( "#members li a" ).bind( "click", function( e ) {} );

    (2)、live 【jQuery 1.3之后】

    定义和用法:主要用于给选择到的元素上绑定特定事件类型的监听函数;

    语法:live(type, [data], fn);

    特点:

    (1)、live方法并没有将监听器绑定到自己(this)身上,而是绑定到了this.context上了。

    (2)、live正是利用了事件委托机制来完成事件的监听处理,把节点的处理委托给了document,新添加的元素不必再绑定一次监听器。

    (3)、使用live()方法但却只能放在直接选择的元素后面,不能在层级比较深,连缀的DOM遍历方法后面使用,即(“ul”").live...可以,但("body").find("ul").live...不行;

    实例如下:$( document ).on( "click", "#members li a", function( e ) {} );

    (3)、delegate 【jQuery 1.4.2中引入】

    定义和用法:将监听事件绑定在就近的父级元素上

    语法:delegate(selector,type,[data],fn)

    特点:

    (1)、选择就近的父级元素,因为事件可以更快的冒泡上去,能够在第一时间进行处理。

    (2)、更精确的小范围使用事件代理,性能优于.live()。可以用在动态添加的元素上。

    实例如下:

    $("#info_table").delegate("td","click",function(){/显示更多信息/});

    $("table").find("#info").delegate("td","click",function(){/显示更多信息/});

    (4)、on 【1.7版本整合了之前的三种方式的新事件绑定机制】

    定义和用法:将监听事件绑定到指定元素上。

    语法:on(type,[selector],[data],fn)

    实例如下:$("#info_table").on("click","td",function(){/显示更多信息/});参数的位置写法与delegate不一样。

    说明:on方法是当前JQuery推荐使用的事件绑定方法,附加只运行一次就删除函数的方法是one()。

    总结 :.bind(), .live(), .delegate(),.on()分别对应的相反事件为:.unbind(),.die(), .undelegate(),.off()

    80.浏览器是如何渲染页面的?

    渲染的流程如下:

    1.解析HTML文件,创建DOM树。

    自上而下,遇到任何样式(link、style)与脚本(script)都会阻塞(外部样式不阻塞后续外部脚本的加载)。

    2.解析CSS。优先级:浏览器默认设置<用户设置<外部样式<内联样式<HTML中的style样式;

    3.将CSS与DOM合并,构建渲染树(Render Tree)

    4.布局和绘制,重绘(repaint)和重排(reflow)

    81.$(document).ready()方法和window.onload有什么区别?

    (1)、window.onload方法是在网页中所有的元素(包括元素的所有关联文件)完全加载到浏览器后才执行的。

    (2)、$(document).ready() 方法可以在DOM载入就绪时就对其进行操纵,并调用执行绑定的函数。

    82. jquery中.get()提交和.post()提交有区别吗?

    相同点:都是异步请求的方式来获取服务端的数据;

    异同点:

    1、请求方式不同:.get() 方法使用GET方法来进行异步请求的。.post() 方法使用POST方法来进行异步请求的。

    2、参数传递方式不同:get请求会将参数跟在URL后进行传递,而POST请求则是作为HTTP消息的实体内容发送给Web服务器的,这种传递是对用户不可见的。

    3、数据传输大小不同:get方式传输的数据大小不能超过2KB 而POST要大的多

    4、安全问题: GET 方式请求的数据会被浏览器缓存起来,因此有安全问题。

    83.对前端路由的理解?前后端路由的区别?

    前端的路由和后端的路由在实现技术上不一样,但是原理都是一样的。在 HTML5 的 history API 出现之前,前端的路由都是通过 hash 来实现的,hash 能兼容低版本的浏览器。

    http://10.0.0.1/
    http://10.0.0.1/#/about
    http://10.0.0.1/#/concat
    

    服务端路由:每跳转到不同的URL,都是重新访问服务端,然后服务端返回页面,页面也可以是服务端获取数据,然后和模板组合,返回HTML,也可以是直接返回模板HTML,然后由前端JS再去请求数据,使用前端模板和数据进行组合,生成想要的HTML。

    前端路由:每跳转到不同的URL都是使用前端的锚点路由,实际上只是JS根据URL来操作DOM元素,根据每个页面需要的去服务端请求数据,返回数据后和模板进行组合,当然模板有可能是请求服务端返回的,这就是 SPA 单页程序。

    在js可以通过window.location.hash读取到路径加以解析之后就可以响应不同路径的逻辑处理。

    history 是 HTML5 才有的新 API,可以用来操作浏览器的 session history (会话历史)。基于 history 来实现的路由可以和最初的例子中提到的路径规则一样。

    H5还新增了一个hashchange事件,也是很有用途的一个新事件:

    当页面hash(#)变化时,即会触发hashchange。锚点Hash起到引导浏览器将这次记录推入历史记录栈顶的作用,window.location对象处理“#”的改变并不会重新加载页面,而是将之当成新页面,放入历史栈里。并且,当前进或者后退或者触发hashchange事件时,我们可以在对应的事件处理函数中注册ajax等操作!
    但是hashchange这个事件不是每个浏览器都有,低级浏览器需要用轮询检测URL是否在变化,来检测锚点的变化。当锚点内容(location.hash)被操作时,如果锚点内容发生改变浏览器才会将其放入历史栈中,如果锚点内容没发生变化,历史栈并不会增加,并且也不会触发hashchange事件。

    84.手写一个类的继承

    继承的形式有很多中,js高程里面归纳了其中,我简单说一下前三种。
    1.原型继承

    function Parent(){
        this.name = "parent";
    }
    Parent.prototype.getName = function(){
        return this.name;
    }
    
    function Child(){
        this.name = "child";
    }
    
    //继承parent
    Child.prototype = new Parent();
    

    2.构造函数继承

    function Animal(name){
        this.name = name;
        this.eat = function(){
            consoel.log(this.name + "吃饭");
        }
    }
    var cat = new Animal("maomi");
    cat.name;
    cat.eat();
    

    3.组合继承

    85.XMLHttpRequest:XMLHttpRequest.readyState;状态码的意思

    XmlHttpRequest(Ajax)状态码readyState
    当一个XMLHttpRequest初次创建时,这个属性的值是从0开始,知道接收完整的HTTP响应,这个值增加到4。有五种状态:

    状态0 (请求未初始化): (XMLHttpRequest)对象已经创建或已被abort()方法重置,但还没有调用open()方法;

    状态1 (载入服务器连接已建立):已经调用open() 方法,但是send()方法未调用,尚未发送请求;

    状态2 (载入完成,请求已接收): send()方法已调用,HTTP请求已发送到web服务器,请求已经发送完成,未接收到响应;

    状态3 (交互,请求处理中):所有响应头部都已经接收到。响应体开始接收但未完成,即可以接收到部分响应数据;

    状态4 (请求完成,且相应已就绪):已经接收到了全部数据,并且连接已经关闭。

    相关文章

      网友评论

        本文标题:js面试题

        本文链接:https://www.haomeiwen.com/subject/tkvjnhtx.html