前端面试题二(js篇)

386 阅读14分钟

1.什么是跨域?解决跨域的方法?

跨域,指的是浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器施加的安全限制。

同源策略

- ——是浏览器安全策略
- ——协议名、域名、端口号必须完全一致
- 举例:
http://www.123.com/index.html 调用 http://www.123.com/server.php (非跨域)

http://www.123.com/index.html 调用 http://www.456.com/server.php (主域名不同:123/456,跨域)

http://abc.123.com/index.html 调用 http://def.123.com/server.php (子域名不同:abc/def,跨域)

http://www.123.com:8080/index.html 调用 http://www.123.com:8081/server.php (端口不同:8080/8081,跨域)

http://www.123.com/index.html 调用 https://www.123.com/server.php (协议不同:http/https,跨域)

请注意:localhost和127.0.0.1虽然都指向本机,但也属于跨域。

浏览器执行javascript脚本时,会检查这个脚本属于哪个页面,如果不是同源页面,就不会被执行

跨域

- 违背同源策略就会产生跨域

解决方法

- jsonp cors websocket Node中间件代理(两次跨域) ngix反向代理...
小提示:如果你回答跨域解决方案CORS,那么面试官一定会问你实现CORS的响应头信息Access-Control-Allow-Origin。

1. jsonp方法

所以JSONP的原理其实就是利用引入script不限制源的特点,把处理函数名作为参数传入,然后返回执行语句,仔细阅读以下代码就可以明白里面的意思了。

补充:1) JSONP和AJAX对比

    JSONP和AJAX相同,都是客户端向服务器端发送请求,从服务器端获取数据的方式。但AJAX属于同源策略,JSONP属于非同源策略(跨域请求)

2)JSONP优缺点

    SONP优点是简单兼容性好,可用于解决主流浏览器的跨域数据访问的问题。缺点是仅支持get方法具有局限性,不安全可能会遭受XSS攻击。
<script>
        //创建 script 标签
        var script = document.createElement('script');
        //设置回调函数
        function getData(data) {
            //数据请求回来被触发的函数
            console.log(data);
        }
        //设置script的src属性,设置请求地址
        script.src = 'http://localhost:3000?callback = getData';
        //让script生效
        document.body.appendChild(script);
    </script>

2. cors(跨域资源共享 Cross-origin resource sharing)

允许浏览器向跨域服务器发出XMLHttpRequest请求,从而克服跨域问题,它需要浏览器和服务器的同时支持。

  • 浏览器端会自动向请求头添加origin字段,表明当前请求来源。
  • 浏览器端需要设置响应头的Access-Control-Allow-Methods,Access-Control-Allow-Headers,Access-Control-Allow-Origin等字段,指定允许的方法,头部,源等信息。
  • 请求分为简单请求和非简单请求,非简单请求会先进行一次OPTION方法进行预检,看是否允许当前跨域请求。

只要同时满足以下两大条件,就属于简单请求

条件1:使用下列方法之一:

  • GET
  • HEAD
  • POST

条件2:Content-Type 的值仅限于下列三者之一:

  • text/plain
  • multipart/form-data
  • application/x-www-form-urlencoded

请求中的任意 XMLHttpRequestUpload 对象均没有注册任何事件监听器; XMLHttpRequestUpload 对象可以使用 XMLHttpRequest.upload 属性访问。

4. websocket

Websocket是HTML5的一个持久化的协议,它实现了浏览器与服务器的全双工通信,同时也是跨域的一种解决方案。WebSocket和HTTP都是应用层协议,都基于 TCP 协议。但是 WebSocket 是一种双向通信协议,在建立连接之后,WebSocket 的 server 与 client 都能主动向对方发送或接收数据。 同时,WebSocket 在建立连接时需要借助 HTTP 协议,连接建立好了之后 client 与 server 之间的双向通信就与 HTTP 无关了。 原生WebSocket API使用起来不太方便,我们使用 Socket.io,它很好地封装了webSocket接口,提供了更简单、灵活的接口,也对不支持webSocket的浏览器提供了向下兼容。

我们先来看个例子:本地文件socket.html向 localhost:3000 发生数据和接受数据

// socket.html
<script>
    let socket = new WebSocket('ws://localhost:3000');
    socket.onopen = function () {
      socket.send('我爱你');//向服务器发送数据
    }
    socket.onmessage = function (e) {
      console.log(e.data);//接收服务器返回的数据
    }
</script>

// server.js
let express = require('express');
let app = express();
let WebSocket = require('ws');//记得安装ws
let wss = new WebSocket.Server({port:3000});
wss.on('connection',function(ws) {
  ws.on('message', function (data) {
    console.log(data);
    ws.send('我不爱你')
  });
})

总结:CORS支持所有类型的HTTP请求,是跨域HTTP请求的根本解决方案 JSONP只支持GET请求,JSONP的优势在于支持老式浏览器,以及可以向不支持CORS的网站请求数据。 不管是Node中间件代理还是nginx反向代理,主要是通过同源策略对服务器不加限制。 日常工作中,用得比较多的跨域方案是cors和nginx反向代理

更多方法参考

2.什么是闭包?

  1. 密闭的容器,类似于set,map容器,存储数据的
  2. 闭包是一个对象,存放数据的格式:key:value 形成的条件:
  3. 函数嵌套
  4. 内部函数引用外部函数的局部变量 闭包的优点:
  5. 延长外部函数局部变量的生命周期 闭包的缺点:
  6. 容易造成内存泄漏 注意点:
  7. 合理使用闭包
  8. 用完闭包要及时清除(销毁)

点击详细讲解

3.axios是什么?怎么使用?描述使用它实现登录功能的流程?

(一).axios是什么?

  1. axios 是基于 promise 用于浏览器和nodejs的一个http客户端,主要用于向后台发送请求的,还有就是在请求中做更多控制。
  2. 支持 promise
  3. 提供了一些并发的方法
  4. 提供拦截器
  5. 提供支持CSRF(跨域请求伪造)

(二).axios fetch ajax(jquery)区别

  1. 前两者都是支持promise的语法,后者默认是使用callback方式
  2. fetch 本质上脱离的xhr 是新的语法(有自己的特性 默认不传cookie 不能像xhr这样 去监听请求的进度)

(三).怎么使用?

<script>
    axios.get('').then(function(){
        
    }).catch(function(){
        
    })
    
    axios.post('').then(function(){
        
    }).catch(function(){
        
    })
</script>

(四).描述使用它实现登录功能的流程?

3.const,var,let区别

点击详细讲解

4.new操作符原理解析

点击详细讲解

5.promise的回调机制

点击详细讲解

6.阻止冒泡,说说vue中事件的阻止冒泡以及原理

点击详细讲解

事件修饰符 在事件处理程序中调用 event.preventDefault() 或 event.stopPropagation() 是非常常见的需求。尽管我们可以在 methods 中轻松实现这点,但更好的方式是:methods 只有纯粹的数据逻辑,而不是去处理 DOM 事件细节。

为了解决这个问题, Vue.js 为 v-on 提供了 事件修饰符。通过由点(.)表示的指令后缀来调用修饰符。

  • .stop
  • .prevent
  • .capture
  • .self
  • .once
<!-- 阻止单击事件冒泡 -->
<a v-on:click.stop="doThis"></a>
 
<!-- 提交事件不再重载页面 -->
<form v-on:submit.prevent="onSubmit"></form>
 
<!-- 修饰符可以串联 -->
<a v-on:click.stop.prevent="doThat"></a>
 
<!-- 只有修饰符 -->
<form v-on:submit.prevent></form>
 
<!-- 添加事件侦听器时使用事件捕获模式 -->
<div v-on:click.capture="doThis">...</div>
 
<!-- 只当事件在该元素本身(比如不是子元素)触发时触发回调 -->
<div v-on:click.self="doThat">...</div>

使用修饰符时,顺序很重要;相应的代码会以同样的顺序产生。因此,用 @click.prevent.self 会阻止所有的点击,而 @click.self.prevent 只会阻止元素上的点击。

7.基本数据类型

  • 基本类型:null、undefined、boolean、number、string、symbol
  • 引用类型:Object(Array,Date,RegExp,Function)
  • tips: NaN 也属于 number 类型,并且 NaN 不等于自身。

那么问题来了,如何判断某变量是否为数组数据类型?

var obj = document.getElementsByTagName("div");
var arr = [1,2,3]

// 请问有几种方式可以确定arr是一个数组?

// 第一种方式:instanceof
instanceof运算符用来判断一个构造函数的prototype属性所指向的对象是否存在另外一个要检测对象的原型上

console.log(arr instanceof Array); //true
console.log(obj instanceof Array); //false
console.log(arr instanceof Object);	//true
console.log(arr instanceof Object); //true

// 第二种方式:查看构造函数
console.log(arr.constructor === Array); //true
console.log(obj.constructor === Array);	//false
// 上面的方式写全为 arr._proto_.constructor === Array

// 第三种方式:转为字符串之后的结果
console.log(Object.prototype.toString.call(arr) === '[object Array]');	//true
console.log(Object.prototype.toString.call(obj) === '[object Array]');	//false

	

8.类型的判断

  • Typeof
console.log(typeof 1);                  // number
console.log(typeof 'a');                // string
console.log(typeof true);               // boolean
console.log(typeof undefined);          // undefined
console.log(typeof function fn(){});    // function
console.log(typeof {});                 // object
console.log(typeof null);               // object
console.log(typeof []);                 // object
console.log(typeof new Error());        // object

tips:typeof 对于基本类型,除了 null 都可以显示正确的类型;对于对象,除了函数都会显示 object

  • Object.prototype.toString
var number = 1;             // [object Number]
var string = '123';         // [object String]
var boolean = true;         // [object Boolean]
var und = undefined;        // [object Undefined]
var nul = null;             // [object Null]
var obj = {a: 1}            // [object Object]
var array = [1, 2, 3];      // [object Array]
var date = new Date();      // [object Date]
var error = new Error();    // [object Error]
var reg = /a/g;             // [object RegExp]
var func = function a(){};  // [object Function]

function checkType() {
    for (var i = 0; i < arguments.length; i++) {
        console.log(Object.prototype.toString.call(arguments[i]))
    }
}

checkType(number, string, boolean, und, nul, obj, array, date, error, reg, func)

9.如果判断两个对象是否相等

先判断对象属性的长度是否相等,再判断每个属性的值是否相等

点击详细讲解

10.javascript两个变量互换值,你了解多少?

点击详细讲解

11.数组去重12种方法?

点击详细讲解

12.this指向问题?

this 的指向取决于函数以哪种方式调用:

  • 作用函数调用:非严格模式下 this 指向全局对象,严格模式为 undefined
  • 作用方法调用:this 指向调用函数的对象
  • 构造函数调用:this 指向 new 创建出来的实例对象
  • call()和apply:它们的第一个参数为 this 的指向
  • 补充:箭头函数中的 this
function a() {
    return () => {
        return () => {
        	console.log(this)
        }
    }
}
console.log(a()()())

箭头函数其实是没有 this 的,这个函数中的 this 只取决于他外面的第一个不是箭头函数的函数的 this。在这个例子中,因为调用 a 符合前面代码中的第一个情况,所以 this 是 window。并且 this 一旦绑定了上下文,就不会被任何代码改变。

13. js 什么方法可以跳出循环

  • break;结束循环推荐使用

  • return 直接跳出方法,如果仅仅只想结束循环不建议使用,因其副作用是,这个方法不再执行

  • 循环变量=最大值/最小值(看你循环是从高数字到低还是低到高,高到低设置成0,低到高设置成数组的length,该方法对for in语句无效)

//循环变量低到高
var arr=[1,2,3,4,5,6,7];
for(var i=0;i<arr.length;i++)
{
  if(arr[i]==4)
   {
     //break;   //方案1
     //return;  //方法后续代码不执行 方案2
     i=arr.length;  //方案3
   }
   console.log(arr[i]); //1,2,3
}
//循环变量从高到低
var arr=[1,2,3,4,5,6,7];
for(var i=arr.length-1;i>-1;i--)
{
  if(arr[i]==4)
   {
     //break;   //方案1
     //return;  //方法后续代码不执行 方案2
     i=-1;      //方案3
   }
   console.log(arr[i]);  //7,6,5
}
//for in情况
//循环变量从高到低
var arr=[1,2,3,4,5,6,7];
for(var i in arr)
{
  if(arr[i]==4)
   {
     break;//方案1
     //return;//方法后续代码不执行 方案2
      //方案3 对此不起作用
   }
   console.log(arr[i]); //1,2,3
}

14. JS 中的主要有哪几类错误?

  • 1.SyntaxError解析错误
  • 2.ReferenceError引用错误
  • 3.RangeError范围错误
  • 4.TypeError类型错误
  • 5.URIError统一资源标识符函数错误
  • 6.EvalError eval()函数执行错误
  • 具体列子参照

15. JS中获取dom元素的方法?

  • 1.getElementById() 最常用的通过元素的id属性来获取DOM元素
  • 2.getElementsByTagName() 通过标签名获取DOM元素的一个集合
  • 3.getElementsByName() 很少用,操作表单 通过元素名来获取DOM元素集合
  • 4.querySelector() 通过合法的CSS选择器获取DOM元素 只获取第一个匹配的元素
  • 5.querySelectorAll() 通过合法的CSS选择器来获元素,返回的是所有符合条件的,而不是第一个符合条件的元素
  • 6.getElementsByClassName()
  • 具体例子参照

16. 用纯JS编写一个程序来反转字符串

var str = "jquery";
str = str.split("");    //字符串转为数组,["j","q","u","e","r","y"]
str = str.reverse();    //数组反转, ["y","r","e","u","q","j"]
str = str.join("");     //数组转字符串,"yreuqj"

17. JS中如何将页面重定向到另一个页面?

* location.href     //window.location.href ="https://www.xx.com/"
* location.replace  //window.location.replace ="https://www.xx.com/"

18. JS中的Array.splice()和Array.slice()方法有什么区别?

var arr=[0,1,2,3,4,5,6,7,8,9];   //设置一个数组
console.log(arr.slice(2,7));    //2,3,4,5,6
console.log(arr.splice(2,7));   //2,3,4,5,6,7,8

//由此我们简单推测数量两个函数参数的意义,
slice(start,end)第一个参数表示开始位置,第二个表示截取到的位置(不包含该位置)
splice(start,length)第一个参数开始位置,第二个参数截取长度
var x=y=[0,1,2,3,4,5,6,7,8,9]
console.log(x.slice(2,5));      //2,3,4
console.log(x);                 //[0,1,2,3,4,5,6,7,8,9]原数组并未改变

//接下来用同样方式测试splice
console.log(y.splice(2,5));     //2,3,4,5,6
console.log(y);                 //[0,1,7,8,9]显示原数组中的数值被剔除掉了

slicesplice虽然都是对于数组对象进行截取,但是二者还是存在明显区别,函数参数上slice和splice第一个参数都是截取开始位置,slice第二个参数是截取的结束位置(不包含),而splice第二个参数(表示这个从开始位置截取的长度),slice不会对原数组产生变化,而splice会直接剔除原数组中的截取数据!

19. 如何在JS中动态添加/删除对象的属性?

var obj = {a:'1',b:'2'}
obj.c = '3';
console.log(obj,'1');       //{a:'1',b:'2',c:'3'}
delete obj.a
console.log(obj,'2');       //{b:'2',c:'3'}

20. 解释一下JS的展开操作符?

var mid = [3, 4];

var newarray = [1, 2, ...mid, 5, 6];

console.log(newarray);          // [1, 2, 3, 4, 5, 6]

21. arr.flat(Infinity)数组扁平化

所谓数组扁平化就是将数组中并不规则的多维数组去除维度,使之变为一维数组。

let newArray = arr.flat(depth)

flat() 方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组返回。 其中,depth指定要提取嵌套数组的结构深度,默认值为1。

但使用 Infinity 作为深度,展开任意深度的嵌套数组 For Example 将数组 var arr = [ [1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14] ] ] ], 10]; 扁平化并去除其中重复数据,最终得到一个升序且不重复的数组。

Array.from 转换为数组 new Set 数组去重 sort 数组或对象某个属性排序

Array.from(new Set(arr.flat(Infinity))).sort((a, b) => { return a - b })

22. JS 异步解决方案的发展历程以及优缺点

回调函数(callback)

setTimeout(() => {
   // callback 函数体
}, 1000)

缺点:回调地狱,不能用 try catch 捕获错误,不能 return

回调地狱的根本问题在于:

  • 缺乏顺序性: 回调地狱导致的调试困难,和大脑的思维方式不符;
  • 嵌套函数存在耦合性,一旦有所改动,就会牵一发而动全身,即(控制反转);
  • 嵌套函数过多的多话,很难处理错误。
ajax('XXX1', () => {
   // callback 函数体
   ajax('XXX2', () => {
       // callback 函数体
       ajax('XXX3', () => {
           // callback 函数体
       })
   })
})

promise

Promise 就是为了解决 callback 的问题而产生的。

Promise 实现了链式调用,也就是说每次 then 后返回的都是一个全新 Promise,如果我们在 then 中 return ,return 的结果会被 Promise.resolve() 包装。

优点:解决了回调地狱的问题。

ajax('XXX1')
 .then(res => {
     // 操作逻辑
     return ajax('XXX2')
 }).then(res => {
     // 操作逻辑
     return ajax('XXX3')
 }).then(res => {
     // 操作逻辑
 })

Async/await

async、await 是异步的终极解决方案。

优点是:代码清晰,不用像 Promise 写一大堆 then 链,处理了回调地狱的问题;

缺点:await 将异步代码改造成同步代码,如果多个异步操作没有依赖性而使用 await 会导致性能上的降低。

async function test() {
 // 以下代码没有依赖性的话,完全可以使用 Promise.all 的方式
 // 如果有依赖性的话,其实就是解决回调地狱的例子了
 await fetch('XXX1')
 await fetch('XXX2')
 await fetch('XXX3')
}

23. 深浅拷贝

拷贝

首先可以通过Object.assign来解决这个问题。

let a = {
    age: 1
}
let b = Object.assign({}, a)
a.age = 2
console.log(b.age) // 1

当然我们也可以通过展开运算符(…)来解决

let a = {
    age: 1
}
let b = {...a}
a.age = 2
console.log(b.age) // 1

深拷贝

function cloneFn( sourse ){
    var o = Object.prototype.toString.call(sourse).toLowerCase().indexOf("array")!==-1 ? [] : {};
    for( var attr in sourse ){
        if( (typeof sourse[attr] === "object") && sourse[attr] !== null ){
            o[attr] = cloneFn( sourse[attr] ) ;
        }else{
            o[attr] = sourse[attr];
        }
    }
    return o;
}

深浅拷贝详细讲解

24. Object.is()与原来的比较操作符"==="、"==” 的区别?

  • ==判等,会在比较时进行类型转换;

  • ===判等(判断严格),比较时不进行隐式类型转换,(类型不同则会返回false);

  • Object.is 在===判等的基础上特别处理了NaN、-0和+0,保证-0和+0不再相同,但Object.is(NaN, NaN)会返回true。Object.is应被认为有其特殊的用途,而不能用它认为它比其它的相等对比更宽松或严格。

Object.is()		//它用来比较两个值是否严格相等,与严格相等运算符(===)的行为基本一致。
Object.is('foo','foo')		//true
Object.is({},{})			//false

// 与等号不同的是
+0 === -0 	//true
NaN === NaN 	//false
Object.js(+0,-0);	//false
Object.is(NaN,NaN);	//true

25. 数组常用的方法

收录干货,就不自己总结了

26. 用js递归的方式写1到100求和

方法一:

function add(num1,num2) {
   var num = num1+num2;
   if(num2+1>100) {
   	return num
   } else {
   	return add(num,num2+1)
   }
}
add(1,2);

方法二:

function add(num) {
  if(num ==1) {
  	return num
  } else {
  	return num + add(num -1)
  }
}
add(100)

27. JS延迟加载的几种方式

  • defer属性
  • async属性
  • 动态创建DOM方式
  • 使用jQery的getScript方法
  • 使用setTimeout延迟方法
  • 让js最后加载 收录干货,戳戳戳

28. 希望获取页面中所有的checkbox怎么做?(不使用第三方框架)

let inputList = document.getElementsByTagName("input")
let len = inputList.length
let checkBoxList = []

while(len --){
  if(inputList[len].type == "checkbox"){
  	checkBoxList.push(inputList(len))
  }
}

29. Object.create 做了什么?

Object._create = function(obj) {
 function F(){};	//创建了一个新的构造函数F
 F.prototype = obj;	//然后将构造函数F的原型指向了参数对象obj
 return new F();	//返回构造函数F的实例对象,从而实现了该实例继承obj的属性
}

30. 浏览器禁用cookie该如何处理

一般会用到url重写的技术来进行会话跟踪,每一次你的交互,都会在url后面加上sid=xxx类似的参数。服务端会根据这种方式来识别用户。

31. 如何中断ajax请求?

种是设置超时时间让ajax自动断开,另一种是手动停止ajax请求,其核心是调用XML对象的abort方法,ajax.abort()