面试题整理

70个JavaScript面试题集锦,内含解答,自测 JS 掌握程度

比上面的多5道

面试题101道

 你不知道的 JSON.stringify() 的威力

 

1. ['1', '2', '3'].map(parseInt)输出结果

['1', '2', '3'].map(parseInt);    // [1,NaN,NaN]

这道题需要理解两个点:

①.parseInt(string, radix)

接收两个参数,第一个表示被处理的值(字符串),第二个表示为解析时的基数。
radix 可选。表示要解析的数字的基数。该值介于 2 ~ 36 之间。如果省略该参数或其值为 0,则数字将以 10 为基础来解析。如果它以 “0x” 或 “0X” 开头,将以 16 为基数。如果该参数小于 2 或者大于 36,则 parseInt() 将返回 NaN。

②.代码实际执行的以下代码

['1', '2', '3'].map((item, index) => {
    return parseInt(item, index)
})
parseInt('1', 0)   // 1
parseInt('2', 1)   // NaN
parseInt('3', 2)   // NaN, 3 不是二进制 string要符合基数的规范

同样的经典题

['10','10','10','10','10'].map(parseInt)
// =>
parseInt('10', 0) // 10
parseInt('10', 1) // NaN
parseInt('10', 2) // 2, 
parseInt('10', 3) // 3, 
parseInt('10', 4) // 4, 

// 所以['10','10','10','10','10'].map(parseInt)  => [10,NaN,2,3,4];

 

2.实现一个函数:输入一个整数,求该整数的二进制表达中有多少个1?

方法1:

function fun1(num){
    return new Number(num).toString(2).split('').filter(item => item == 1).length 
}
fun1(9)   // 2

方法2:

function fun2(num){
    var len = 0;
    while(num > 0) {
        var a = num % 2;
        num = (num - a) / 2;
        if(a === 1) {
            len++;
        }
    }
    return len;
}
fun2(9)

 

3.实现一个函数:给定一个字符串,找出出现次数最多的字符及次数

function getMaxStr(str) {
    let json = {};
    for(let i =0;i<str.length;i++){
        if(json[str[i]]) {
            json[str[i]]++
        }else {
            json[str[i]] = 1;
        }
    }
    let maxNum = 0;
    let maxStr = '';
    for(let key in json){
        if(json[key] > maxNum){
            maxNum = json[key];
            maxStr = key;
        }
    }
    console.log(`出现次数最多的字符是${maxStr},出现次数是${maxNum}`)
}
getMaxStr('ddddssfes')    // 出现次数最多的字符是d,出现次数是4

 

4.this指向理解

var a = 1;
var obj = {
    a: 2,
    func1: ()=>{console.log(this.a)},
    func2: function(){console.log(this.a)}
}

var obj2 = {a: 3};
console.log(obj.func1())    // 1
console.log(obj.func2())    // 2
obj.func2.apply(obj2)       // 3
var newFunc = obj.func2;
newFunc()    //1

tips: 函数直接被调用时this则指向window,函数作为某对象的方法调用时,this指向该对象,而箭头函数没有执行上下文,取决于他就近的外面的一层非箭头函数的函数。

箭头函数的this看外层的是否有函数,如果有,外层函数的this就是内部箭头函数的this,如果没有,则this是window

 

5.下面三段代码分别输出什么?并且什么时候输出什么?

for (var i = 0; i < 5; i++) {
  setTimeout(function () {
    console.log(i)
  }, 1000 * i)
} 
// 在极短的一段时间内输出5,随后每隔一秒输出一个5
// 5 5 5 5 5
// for循环结束后才到宏观任务setTimeout,这时候i的值已经变为5了
for (let i = 0; i < 5; i++) { setTimeout(function () { console.log(i) }, 1000 * i) } // 在极短的一段时间内输出0,随后每隔一秒结果加1 // 0 1 2 3 4
// let i 是块作用域
for (var i = 0; i < 5; i++) { (function (i) { setTimeout(function () { console.log(i) }, 1000 * i) })(i) } // 在极短的一段时间内输出0,随后每隔一秒结果加1 // 0 1 2 3 4
// 立即执行函数,创建了属于自己的作用域,因此每一次执行都是不同的i

事件运行机制

 

6.自由变量理解 

var x = 10
function fn() {
  console.log(x)
}
function show(f) {
  var x = 20
  (function() {
    f() //10,而不是20
  })()
}
show(fn)

什么是自由变量? --当前作用域没有定义的变量,这成为自由变量 

var a = 100
function fx() {
    var b = 200
    console.log(a) // 这里的a在这里就是一个自由变量
    console.log(b)
}
fx()

在fn函数中,取自由变量x的值时,要到哪个作用域中取?--要到创建fn函数的那个作用域中取,无论fn函数将在哪里调用。

要到创建这个函数的那个域。作用域中取值,这里强调的是“创建”,而不是“调用”,切记切记——其实这就是所谓的"静态作用域"。

 

7. 闭包的理解

function outer(){
  var num=0;//内部变量
  return function add(){//通过return返回add函数,就可以在outer函数外访问了
      num++;//内部函数有引用,作为add函数的一部分了
      console.log(num);
  };
}
var func1=outer();
func1();
//实际上是调用add函数, 输出1 func1();//输出2 因为outer函数内部的私有作用域会一直被占用 var func2=outer(); func2();// 输出1 每次重新引用函数的时候,闭包是全新的。 func2();// 输出2

一般情况下,函数执行会形成一个新的私有的作用域,当私有作用域中的代码执行完成后,我们当前作用域都会主动的进行释放和销毁。但当遇到函数执行返回了一个引用数据类型的值,并且在函数的外面被一个其他的东西给接收了,这种情况下一般形成的私有作用域都不会销毁。

所谓内存泄漏指任何对象在您不再拥有或需要它之后仍然存在。闭包不能滥用,否则会导致内存泄露,影响网页的性能。闭包使用完了后,要立即释放资源,将引用变量指向null。

 

8.手写简化版防抖函数、节流函数

// 防抖函数 防抖函数原理:在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。 只执行最后提交的一次
const debounce = (fn, delay = 500) => {
  let timer = null;
  return () => {
    clearTimeout(timer);
    timer = setTimeout(() => {
      fn();
    }, delay);
  };
};

// 节流函数 防抖函数原理:规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。
const throttle = (fn, delay = 500) => {
  let flag = true;
  return () => {
    if (!flag) return;
    flag = false;
    setTimeout(() => {
      fn();
      flag = true;
    }, delay);
  };
};

 

 9.说出下面运行的结果,解释原因 

function test(person) {
    person.age = 26;
    person = {
        name: 'hzj',
        age: 18
    };
    return person;
}
const p1 = {
    name: 'fyq',
    age: 19
};
const p2 = test(p1);
console.log(p1);   //{name: “fyq”,age: 26}
console.log(p2);   //{name: “hzj”,age: 18}

 

原因: 在函数传参的时候传递的是对象在堆中的内存地址值,test函数中的实参person是p1对象的内存地址,通过调用person.age = 26确实改变了p1的值,但随后person变成了另一块内存空间的地址,并且在最后将这另外一份内存空间的地址返回,赋给了p2。

 

 10.new 关键字有什么作用 

new关键字与构造函数一起使用以创建对象在JavaScript中。

下面看看例子:

function Employee(name, position, yearHired) {
  // creates an empty object {}
  // then assigns the empty object to the "this" keyword
  // this = {};
  this.name = name;
  this.position = position;
  this.yearHired = yearHired;
  // inherits from Employee.prototype
  // returns the "this" value implicitly if no
  // explicit return statement is specified
};

const emp = new Employee("Marko Polo", "Software Developer", 2017);

new关键字做了4件事:

  • 创建空对象 {}

  • 将空对象分配给 this 值

  • 将空对象的__proto__指向构造函数的prototype

  • 如果没有使用显式return语句,则返回this

根据上面描述的,它将首先创建一个空对象{},然后它将this值赋给这个空对象this={},并向这个对象添加属性。因为我们没有显式的return语句,所以它会自动为我们返回this。

 

11. http常见的响应码

 

 

 

 常见状态码:

  • 200 请求成功
  • 301 永久重定向,一般是地址发生变化
  • 302 临时重定向
  • 304 Not Modified(通常是在协商缓存中表示本次内容未修改)
  • 400 Bad Request - 前端请求错误
  • 401 Unauthorized - 未授权
  • 403 Forbidden - 请求被拒绝
  • 404 Not Found - 资源不存在
  • 500 Inter Server Error - 服务端异常
  • 503 Server Unavailable - 服务不可用

 

12.前端鉴权

鉴权主要分为四种:

  • HTTP Basic Authentication (HTTP基本认证)
  • session-cookie
  • Token 验证(包括JWT,SSO)
  • OAuth(开放授权)

我们普通网站常用的认证就是session-cookie的方式,用户向服务端发生请求,服务端会创建session并保存相关身份信息,并向客户端下发一个sessionId,大家如果用心的话,会发现跟JAVA交互的时候,浏览器会有一个JSESSION_ID,跟PHP交互的时候,会有一个PHPSESSION_ID;后面的每次请求,客户端都会自动带上这个cookie跟服务端通信。

实际上大家要明白每一种方式的作用;SSO主要用来做单点登录;OAuth主要用来做第三方网站授权;JWT就是一种便于扩展的跨域认证解决方案,通常会考察这个。

给大家推荐阮一峰的JWT讲解 

 

OAuth2.0原理

讲原理主要是让你对这个过程做梳理,并不要求对源码过程做剖析,所以可以通过一个简单的流程来进行回答。我通过微信的授权登录来给大家做讲解:OAuth2.0是一个开源的授权认证方案。当我们登录一个网站时,如果想要通过微信做授权登录,从而获取微信的用户信息,正常情况肯定是不允许,通过微信开放的OAuth2.0我们可以做授权认证。我们点击自己网站的微信按钮,跳转一个链接,这个链接比如是:https://open.weixin.qq.com/connect/oauth2/authorize?会跳转到微信那边去让用户同意授权,用户同意以后,会重定向回来并携带一个code,此code是微信下发的临时凭证。开发者拿到此code以后,就可以获取access_token,根据下发的token,我们才能有权限获取其它接口信息。

  • 点击按钮,跳转第三方授权网站
  • 用户同意授权,重定向回来携带code
  • 开发者根据code获取微信access_token
  • 拿到token拉取用户资料

回答的时候,能够把这个过程描述清楚就好,不要过多解析源码。更进一步了解OAuth2.0可参考阮一峰教程:http://www.ruanyifeng.com/blog/2019/04/oauth-grant-types.html

OAuth2.0的四种方式

  • 授权码(最常用,适用于有后端的web)
  • (授权码)隐藏式 (适用于没有后端的web,少了拿授权码步骤,直接拿到令牌)
  • 密码式 (通过用户给出用户名及密码获取令牌,风险大,在其他授权方式无法获取到令牌且高度信任该应用时下适用)
  • 客户端凭证 (适用于没有前端的命令行应用,这种方式给出的令牌,是针对第三方应用的,而不是针对用户的,即有可能多个用户共享同一个令牌。)

授权码图示

 

 

(授权码)隐藏式图示

A 网站拿到令牌以后,就可以向 B 网站的 API 请求数据了。

此时,每个发到 API 的请求,都必须带有令牌。具体做法是在请求的头信息,加上一个Authorization字段,令牌就放在这个字段里面。

 

更新令牌 

令牌的有效期到了,如果让用户重新走一遍上面的流程,再申请一个新的令牌,很可能体验不好,而且也没有必要。OAuth 2.0 允许用户自动更新令牌。

具体方法是,B 网站颁发令牌的时候,一次性颁发两个令牌,一个用于获取数据,另一个用于获取新的令牌(refresh token 字段)。令牌到期前,用户使用 refresh token 发一个请求,去更新令牌。

 

 

 

 

 

 

 

未完,待续...

 

posted @ 2019-09-17 17:28  JSKevin  阅读(268)  评论(0编辑  收藏  举报