最全面试题JavaScript(含答案)

1、call、apply、bind的区别

这三个都是用来定义上下文的,call、apply会指定上下文并执行函数;而bind终身定 死上下文但是不执行函数,并返回新的函数。 其中call和apply传入参数的形式有别,call是单独罗列,逗号隔开参数;apply是数 组。 函数.call(上下文对象,参数,参数,参数); 函数.apply(上下文对象,[参数,参数,参数]);

var obj = {
 a: 10
}
​
function fun(b, c){
 console.log(this.a + b + c);
}
​
fun.call(obj, 3, 4);
fun.apply(obj, [3, 4]);
fun = fun.bind(obj); // 返回新的函数
fun(3,4);


2、数据类型有哪些

基本类型:数字number、字符串string、布尔boolean、undefined、null、symbol

引用类型:数组array、函数function、对象object


3、如何检测数据类型

typeof 能够检测:数字、字符串、布尔、undefined、symbol、function

instanceof 能够检测:数组

Object.prototype.toString.call() 万能法

扫描二维码关注公众号,回复: 17080242 查看本文章


4、各语句的区别

4.1、for和for...in和for...of的区别

for循环,遍历整个数组

for...in加强循环,不光可以遍历数组,还可以遍历对象和其原型上的方法

for...of遍历数组和可枚举的对象

4.2、switch和if的区别

switch用于判断精准的值

if用于判断值的范围

4.3、while和do...while的区别

while当符合条件时则执行

do...while先执行一次,然后再判断是否符合条件,比while要多执行一次

4.4、break和continue的区别

break是跳出当前循环并终止循环

continue是跳出当前循环并执行下一次循环


5、闭包

闭包就是函数能够记忆住当初定义时候的作用域,不管函数到哪里执行了,永远都能够 记住那个作用域,并且会遮蔽新作用域的变量。可预测状态容器;实现模块化,实现变量的私有封装;可以实现迭代器。 闭包缺点:1.闭包有一个非常严重的问题,那就是内存浪费问题,这个内存浪费不仅仅 因为它常驻内存,更重要的是,对闭包的使用不当的话会造成无效内存的产生;2.性能问题 使用闭包时,会涉及到跨作用域访问,每次访问都会导致性能损失。 因此在脚本中,最好小心使用闭包,它同时会涉及到内存和速度问题。不过我们可以通过把跨作用域变量存储在局部变量中,然后直接访问局部变量,来减轻对执行速度的影响。

      function foo(){
        var a = 0;
        function f(){
          a++;
          return a;
        }
        return f;
      }
      var res = foo();
      res(); 
      res();
      res();
      console.log(res()); // 4 a的值被存储在内存中不会被释放掉


6、原型和原型链

原型:每一个对象类型都有一个隐式原型__ proto __ ,每一个函数都有一个显示原型prototype,该属性指向它的原型对象。

原型链:某个对象的原型又有自己的原型,直到某个对象的原型为null为止,组成这条的最后一环,这种一级一级的链就是原型链。


7、继承

7.1、原型链继承

 /**
 * 缺点:引用类型的属性被所有实例共享,
 * 在创建Child 的实例时, 不能向Person传参
 */
   function Person() {
    this.name = "xiaopao";
   }
   Person.prototype.getName = function () {
    console.log(this.name);
   };
   function Child() {}
   Child.prototype = new Person();

7.2、借用构造函数继承(经典继承)

/*
优点:
1.避免了引用类型的属性被所有实例共享
2.可以在Child中向Parent传参
缺点:
1.只是子类的实例,不是父类的实例
2.方法都在构造函数中定义,每次创建实例都会创建一遍方法
*/
   function Child() {
    Person.call(this);
   }

7.3、组合继承

/*
优点:融合原型链继承和构造函数的优点,是JavaScript中最常用的继承模式
缺点:调用了两次父类构造函数
组合继承最大的问题是无论什么情况下,都会调用两次超类型构造函数:一次是在创建子
类型原型的时候,另一次是在子类型构造函数内部)
*/
   function Child(name, age) {
    Parent.call(this, name); // 第二次调用 Parent()
    this.age = age;
   }
   Child.prototype = new Parent(); // 第一次调用 Parent()

7.4、原型式继承

 // 缺点: 包含引用类型的属性值始终都会共享相应的值, 这点跟原型链继承一样
   function CreateObj(o) {
    function F() {}
    F.prototype = o;
    return new F();
   }
   var person = {
    name: "xiaopao",
    friend: ["daisy", "kelly"],
   };
   var person1 = CreateObj(person);

7.5、寄生式继承 可以理解为在原型式继承的基础上增加一些函数或属性

 // 缺点:跟借用构造函数一样,每次创建对象都会创建一遍方法
​
   var ob = {
    name: "xiaopao",
    friends: ["lulu", "huahua"],
   };
​
   function CreateObj(original){
    var clone = Object.create(original);    //通过调用函数创建一个新对象
    clone.sayHi = function(){               //以某种方式来增强这个对象
        alert("Hi");
    };
    return clone;                        //返回这个对象
   }
​
   // 上面CreateObj函数 在ECMAScript5 有了一新的规范写法,Object.create(ob) 效果是一样的 , 看下面代码
   var ob1 = CreateObj(ob);
   console.log(ob1.name); // xiaopao

7.6、寄生组合式继承

// 优点:完美继承
// 缺点:代码繁多,使用起来十分麻烦
        function Parent(name) {
            this.name = name;
        }
​
        Parent.prototype.sayName = function () {
            console.log(this.name);
        };
​
        function Child(name) {
            Parent.call(this, name);
        }
​
        function CreateObj(o) {
            function F() { }
            F.prototype = o;
            return new F();
        }
​
        function prototype(child, parent) {
            var prototype = CreateObj(parent.prototype);
            prototype.constructor = child;
            child.prototype = prototype;
        }
        prototype(Child, Parent);
​
​
        var child = new Child("大圣");
        child.sayName();
        console.log(child);

7.7、es6 继承

 class Child extends Parent {}


8、递归和递归优化

递归就是函数自己调用自己。但是又不能无限的调用自己,需要有一个出口,否则会成为死循环。函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出。

// 循环求1-5的所有数的和
var sum = 0;
for(var i = 1; i <= 5; i++){
    sum += i;
}
console.log(sum) // 15
​
//递归实现1-5的所有数的和
function sum(n){
    if(n === 1){
        return 1;
    }
    return n + sum(n-1);
}
console.log(sum(5)); //15

尾递归优化是解决递归调用栈溢出的方法。尾递归是指,在函数返回的时候,调用自身本身,并且,return语句不能包含表达式。这样,编译器或者解释器就可以把尾递归做优化,使递归本身无论调用多少次,都只占用一个栈帧,不会出现栈溢出的情况。

// 上例递归进行尾递归优化
function sum(n, m = 0){
    if(n === 1){
        return 1 + m;
    }
    return sum(n-1, n + m);
}
console.log(sum(5)); //15
​
// 或者while优化
function sum(n, m = 0){
    while(n >= 1){
        return sum(n - 1, n + m);
    }
    return m;
}
console.log(sum(5)); // 15
​


9、ajax工作原理和封装

1.创建XMLHttpRequest对象。 2.设置请求方式。open() 3.调用回调函数。onreadystatechange 4.发送请求。send()

     function ajax(options) {
        const { type, dataType, data, timeout, url, success, error } = options;
        var params = formatParams(data);
        var xhr;
        //考虑兼容性
        if (window.XMLHttpRequest) {
          xhr = new XMLHttpRequest();
        } else if (window.ActiveObject) {
          //兼容IE6以下版本
          xhr = new ActiveXobject("Microsoft.XMLHTTP");
        }
        //启动并发送一个请求
        if (type == "GET") {
          xhr.open("GET", url + "?" + params, true);
          xhr.send();
        } else if (type == "POST") {
          xhr.open("post", url, true);
          //设置表单提交时的内容类型
          //Content‐type数据请求的格式
          xhr.setRequestHeader(
            "Content‐type",
            "application/x‐www‐form‐urlencoded"
          );
          xhr.send(params);
        }
​
        // 设置有效时间
        setTimeout(function () {
          if (xhr.readySate != 4) {
            xhr.abort();
          }
        }, timeout);
​
        // 接收
        // options.success成功之后的回调函数 options.error失败后的回调函数
        //xhr.responseText,xhr.responseXML 获得字符串形式的响应数据或者XML形式的响应数据
        xhr.onreadystatechange = function () {
          if (xhr.readyState == 4) {
            var status = xhr.status;
            if ((status >= 200 && status < 300) || status == 304) {
              success && success(xhr.responseText, xhr.responseXML);
            } else {
              error && error(status);
            }
          }
        };
      }
​
      //格式化请求参数
      function formatParams(data) {
        var arr = [];
        for (var name in data) {
          arr.push(
            encodeURIComponent(name) + "=" + encodeURIComponent(data[name])
          );
        }
        arr.push(("v=" + Math.random()).replace(".", ""));
        return arr.join("&");
      }


10、跨域

跨域是指一个域下的文档或脚本试图去请求另一个域下的资源,这里跨域是广义的。其 实我们通常所说的跨域是狭义的,是由浏览器同源策略限制的一类请求场景。同源策略SOP(Same origin policy)是一种约定,由Netscape公司1995年引入浏览器,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS、CSFR 等攻击。所谓同源是指"协议+域名+端口"三者相同,即便两个不同的域名指向同一个ip地 址,也非同源。

方法1:跨域资源共享CORS跨域,就是服务端在HTTP返回头上加上“AccessControll-Allow-Origin:*”。 “Access-Controll-Allow-METHODS:GET, POST” DELETE、PATCH请求类型会发出OPTIONS预检请求。

方法2:代理跨域,webpack-dev-server里面的proxy配置项。config中的 ProxyTable

方法3:JSONP,利用页面srcipt没有跨域限制的漏洞,用script的src引入它,然后页 面内定义回调函数,jQuery中$.ajax({dataType: ‘jsonp’})。

方法4: iframe跨域,配合window.name或者 location.hash或者document.domain 一起使用

方法5:nginx反向代理接口跨域,通过nginx配置一个代理服务器(域名与domain1 相同,端口不同)做跳板机,反向代理访问domain2接口,并且可以顺便修改cookie中 domain信息,方便当前域cookie写入,实现跨域登录。

方法6:jquery的ajax跨域,dataType:'jsonp'


11、事件流和事件委托

事件流一般分三个阶段:1、捕获阶段(由外向内) 2、目标阶段 (执行阶段) 3、冒泡阶段(由内向外)

阻止事件冒泡e.stopPropagation() 阻止默认动作e.preventDefault()

事件委托:就是把事件委托给父级,利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。


12、事件循环

同步任务进入主线程,异步任务进入Event Table并注册函数 当指定的事情完成时,Event Table会将这个函数移入Event Queue。 主线程内的任务执行完毕为空,会去Event Queue读取对应的函数,进入主线程执 行。 上述过程会不断重复,也就是常说的Event Loop(事件循环)。

      console.log("script start");
​
      setTimeout(function () {
        console.log("setTimeout");
      }, 0);
​
      Promise.resolve()
        .then(function () {
          console.log("promise1");
        })
        .then(function () {
          console.log("promise2");
        });
​
      console.log("script end");
      /* 执行结果为:script start, script end, promise1, promise2, setTimeout因为Promise是微任务,主线程会在同步任务做完后先清空微任务队列,再执行宏任务队列 */

微任务是由JavaScript自身发起,包括:process.nextTick、promise、MutationObserver

宏任务是由宿主发起的,如浏览器、node。包括:setTimeout、setInterval、setImmediate、postMessage


13、防抖和节流

// 节流:在计时器内部清除计时器,有节奏的执行事件
        function throttle(callback, delay = 1000){
            let timer = null;
            function f(){
                if(!timer){
                    timer = setTimeout(() => {
                        callback && callback.call(this);
                        clearTimeout(timer);
                        timer = null;
                    }, delay);
                }
            }
            return f;
        }
      
// 防抖:在计时器前边清除计时器,只执行最后一次事件,能够无限延长执行时间
        function debounce(callback, delay = 1000) {
            let timer = null;
            function f() {
                clearTimeout(timer);
                timer = setTimeout(() => {
                    callback && callback.call(this);
                }, delay);
            }
            return f;
        }


14、深克隆和浅克隆

浅克隆:同值也同址。浅拷贝是指源对象与拷贝对象共用一份实体,仅仅是引用的变量不同(名称不同)。对其中任何一个对象的改动都会影响另外一个对象。如:Object.assign;=等号赋值;slice截取。

深克隆:同值不同址。深拷贝是指源对象与拷贝对象互相独立,其中任何一个对象的改动都不会对另外一个对象造成影响。如:JSON.parse(JSON.stringify());

      function deepClone(target) {
        // 定义一个变量
        let result;
        // 如果当前需要深拷贝的是一个对象的话
        if (typeof target === "object") {
          // 如果是一个数组的话
          if (Array.isArray(target)) {
            result = []; // 将result赋值为一个数组,并且执行遍历
            for (let i in target) {
              // 递归克隆数组中的每一项
              result.push(deepClone(target[i]));
            }
            // 判断如果当前的值是null的话;直接赋值为null
          } else if (target === null) {
            result = null;
            // 判断如果当前的值是一个RegExp对象的话,直接赋值
          } else if (target.constructor === RegExp) {
            result = target;
          } else {
            // 否则是普通对象,直接for in循环,递归赋值对象的所有值
            result = {};
            for (let i in target) {
              result[i] = deepClone(target[i]);
            }
          }
          // 如果不是对象的话,就是基本数据类型,那么直接赋值
        } else {
          result = target;
        }
        // 返回最终结果
        return result;
      }


15、cookie、sessionStorage和localStorage的区别

15.1、cookie数据始终在同源的http请求中携带(即使不需要),即cookie在浏览器和服务器间来回传递,而sessionStorage和localStorage不会自动把数据发送给服务器,仅在本地保存。cookie数据还有路径(path)的概念,可以限制cookie只属于某个路径下 15.2、存储大小限制也不同,cookie数据不能超过4K,同时因为每次http请求都会携带cookie、所以cookie只适合保存很小的数据,如会话标识。sessionStorage和localStorage虽然也有存储大小的限制,但比cookie大得多,可以达到5M或更大 15.3、数据有效期不同,sessionStorage:仅在当前浏览器窗口关闭之前有效;localStorage:始终有效,窗口或浏览器关闭也一直保存,因此用作持久数据;cookie:只在设置的cookie过期时间之前有效,即使窗口关闭或浏览器关闭 15.4、作用域不同,sessionStorage不在不同的浏览器窗口中共享,即使是同一个页面;localstorage在所有同源窗口中都是共享的;cookie也是在所有同源窗口中都是共享的 15.5、web Storage支持事件通知机制,可以将数据更新的通知发送给监听者 15.6、web Storage的api接口使用更方便


16、get和post请求的区别

GET在浏览器回退时是无害的,而POST会再次提交请求。

GET产生的URL地址可以被Bookmark,而POST不可以。

GET请求会被浏览器主动cache,而POST不会,除非手动设置。

GET请求只能进行url编码,而POST支持多种编码方式。

GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留。

GET请求在URL中传送的参数是有长度限制的,而POST么有。

对参数的数据类型,GET只接受ASCII字符,而POST没有限制。

GET比POST更不安全,因为参数直接暴露在URL上,所以不能用来传递敏感信息。

GET参数通过URL传递,POST放在Request body中


17、new操作符都做了哪些事情

构造函数中没有显示的创建Object对象,实际上后台自动创建了一个空对象,直接给this对象赋值属性和方法,this即指向创建的对象。没有return返回值,后台自动返回了该对象,该对象继承构造函数的原型

// 模拟构造函数实现
 var Book = function(name) {
   this.name = name;
 };
 //正常用法
 var js = new Book('js');
 //使用代码模拟,在非IE浏览器中测试,IE浏览器不支持
 var javascript = {};
 javascript.__proto__ = Book.prototype;
 Book.call(javascript, 'js');


18、XSS攻击和CSRF攻击

XSS:跨站脚本攻击Cross site script,因叫css容易让人误会所以改成了xss。比如一个JSON数据:

      var obj = [
        {
          id: 1,
          name: "<script>alert('哈哈哈')</script>",
          age: 12,
        }
      ];

在不该出现script代码的地方出现了,引发一些潜在的危险。 XSS漏洞,能让人们在网页里面插入一段有功能的语句。 XSS 全称“跨站脚本”,是注入攻击的一种。其特点是不对服务器端造成任何伤害, 而是通过一些正常的站内交互途径,例如发布评论,提交含有 JavaScript 的内容文本。这时服务器端如果没有过滤或转义掉这些脚本,作为内容发布到了页面上,其他用户访问这个 页面的时候就会运行这些脚本。 防范: ① 用正则表达式阻止用户提交带有<、eval、script等危险字眼的语句 ② 显示的时候不要直接用innerHTML,而是用innerText,或者将<转义。

CSRF 的全称是“跨站请求伪造”,而 XSS 的全称是“跨站脚本”。看起来有点相 似,它们都是属于跨站攻击——不攻击服务器端而攻击正常访问网站的用户,但前面说 了,它们的攻击类型是不同维度上的分类。CSRF 顾名思义,是伪造请求,冒充用户在站内 的正常操作。我们知道,绝大多数网站是通过 cookie 等方式辨识用户身份(包括使用服务 器端 Session 的网站,因为 Session ID 也是大多保存在 cookie 里面的),再予以授权的。 所以要伪造用户的正常操作,最好的方法是通过 XSS 或链接欺骗等途径,让用户在本机(即 拥有身份 cookie 的浏览器端)发起用户所不知道的请求。 就是说,如果用户不老老实实写姓名,写了一个个<script>叫做XSS。如果进一步的,写了一个$.post()发了document.cookie就是CSRF了。解决方法: ① 用token验证,验证用户的IP地址生成MD5码,更安全的验证方法 ② 防住XSS。


19、垃圾回收机制

一般来说没有被引用的对象就是垃圾,就是要被清除, 有个例外如果几个对象引用形成一个环,互相引用,但根本访问不到它们,这几个对象也是垃圾,也要被清除。 垃圾回收的方法主要有两种:一种是标记清除,即用完之后的变量 赋值成null,另一种 是引用计数,将使用完的对象的引用计数,如果为0则回收


20、常用DOM操作

createElement 创建

appendChild末尾添加

insertBefore 前边插入

cloneNode(true) 克隆

removeChild() 移除

parentNode父节点

childNodes // 全部子节点

firstChild // 第一个子节点

lastChild // 最后一个子节点

previousElementSibling// 上一个兄弟节点

nextElementSibling// 下一个兄弟节点

获取dom节点:document.getElementById() 、document.getElementsByTagName() 、document.getElementsByClassName() 、document.getElementsByName() 、document.querySelector() 、document.querySelectorAll()


21、AMD、CMD、ES6、CommonJS的区别

CommonJS:模块引用(require) 模块输出(exports) 模块标识(module) ES6:模块引用(import) 模块输出(export) 前者支持动态导入,也就是 require(${path}/xx.js),后者目前不支持。 前者是同步导入,因为用于服务端,文件都在本地,同步导入即使卡住主线 程影响也不大。而后者是异步导入,因为用于浏览器,需要下载文件,如果也采用同 步导入会对渲染有很大影响。 前者在导出时都是值拷贝,就算导出的值变了,导入的值也不会改变,所以 如果想更新值,必须重新导入一次。但是后者采用实时绑定的方式,导入导出的值都 指向同一个内存地址,所以导入值会跟随导出值变化 AMD、CMD都使用define定义模块,require引入模块,区别在于AMD是前置依赖, CMD是就近依赖

      // AMD   依赖必须一开始就声明
      define(["./a", "./b"], function (require, factory) {
        // do something...
      });
​
      // CMD  
      define(function(require, factory) {
        var a = require('./a'); // 依赖就近书写
        // do something...
      });

猜你喜欢

转载自blog.csdn.net/xiaozgm/article/details/125750137
今日推荐