目录

一、前言

二、问题

1,JS数据类型

2,== 与 === 的区别?

3,JS的宏任务和微任务?

4,实现Jsonp

5,JS作用域

6,JS作用域+this指向+原型笔试题(高频)

7, JS判断变量是不是数组,有哪些方法?

8, slice是干嘛的,splice是否会改变原数组,二者区别?

9,JS数组去重?

10,找出多维数组的最大值?

12,找出一个字符串出现次数最多的字符,出现了多少次?

13,字符串的方法?

14,new操作符具体做了什么?

15,闭包是什么?

16,原型链是什么?

17,js的继承有哪些方式?

18,call、apply、bind的区别?

19,sort原理了解吗?

20,深浅拷贝的理解?(高频)

21,本地存储方式的区别?

22,find和filter区别?

23,some和every区别?

24,合并多个对象?


一、前言

一般比较大一点的公司面试或笔试问题的重点都侧重于js这块,考验你js的功底扎不扎实。所以在准备面试的时候,js一定要复习好,而且要过好几遍,直至熟练回答为止,有的甚至需要手写几遍。毕竟机会都是给有准备的人的。下面给大家整理了一下面试中常问的js问题。大家可以查缺补漏。

二、问题

1,JS数据类型

基本数据类型 Number、String、Boolean、Null、Undefined、Symbol

引用数据类型 Object、Array、Date、Function、RegExp

这里面可以拓展的问题很多,一定要多去思考,比如:

  • null和undefined的区别?(基本的大家都知道,但怎么回答的让面试官耳目一新呢?)

(1)设计历史:
之前看过相关文献,作者在设计js的时候,先有null后有undefined,因为借鉴了java的语言,null的数据类型时object,会被隐式转换成0,不容易发现错误;所以就设计了undefined,它是一个基本数据类型,转成数值为NaN;
(2)数据:
null已经声明赋值为空;引用数据类型;数值转换为0;
undefined已经声明未赋值;基本数据类型;数值转换为NaN(数值类型,不表示数字);

  • symbol数据类型的特点?

symbol 是ES6引入了一种新的基本数据类型(原始数据类型),表示独一无二的值。

(1)Symbol的值是唯一的,用来解决命名冲突的问题;
(2)Symbol值不能与其他数据类型进行运算;
(3)Symbol定义得的对象的属性不能使用for…in 循环遍历,但是可以使用Reflect.ownKeys来获取对象的所有键名;

  • 两种数据类型的存放机制?

基本数据类型体积小,存放在栈里面;
引用数据类型体积大,存放在堆里面;
引用数据类型会有个指针放在栈里面,定位堆里面存放的引用数据;

  • 如何判断这两种数据类型?

(1)typeof:查找基本数据类型,引用数据类型除了function,其他都为object;
(2)instanceof:查找引用数据类型;

原理:instanceof 在查找的过程中会遍历左边变量的原型链,直到找到右边变量的 prototype, 返回true,不是返回false;
(3)Object.prototype.toString().call():所有数据类型;

原理:原型链和原型有关,首先toString()这个方法本来应该返回string的字符串形式,但是大多数情况会返回[object,xxxx]形式;因为js重写了某些数据类型的toString()方法;所以这时候我们找到原型上最原始的toSting()方法;call的作用就是转变this的指向;

  • 数据类型如何转换?

(1)转为字符串:toString() / String() 不能用于null和undefined

(2)转为数值:Number() / parseInt() 转为整数,parseFloat 转为浮点数

(3)隐式转换:js是一门弱语言,运行期间,变量会根据运行环境自动类型转换;

举例:字符串数据+0/*1可以转换成数值;字符串+数值=字符串

(4)转为布尔值:false(0,null,undefined,'',NaN)

2,== 与 === 的区别?

==比较值
string == number || boolean || number都会隐式转换,通过valueOf()方法通常由js在后台自动转换
===比较值和数据类型,除了比较值,还比较类型

3,JS的宏任务和微任务?

(1)首先得解释一下JS的单线程:

js是单线程的语言,用途(浏览器的脚本语言,主要负责交互,动画,效果)决定了它必须是单线程的语言。单线程是指同一时间只能做一件事。如果能做多件事情的话,假如用户同时有两个操作,一个是删除节点,一个是新增节点,那到底应该选择是新增还是删除呢?所以js在设计的时候就必须是单线程的。

(2)其次解释一下JS代码的执行流程:

同步执行完==》事件循环;同步的任务都执行完了,才会执行事件循环的内容,进入事件循环有哪些:请求、定时器、事件。

(3)然后解释一下事件循环:

进入事件循环也得分哪个先哪个后呀?所以事件循环里面又分微任务和宏任务。
微任务:promise.then();
宏任务:setTimeOut;
执行顺序为:先执行微任务,在执行宏任务,宏任务里面还有微任务,先执行微任务,以此循环下去;记住关键,要执行宏任务的前提是清空了所有的微任务。

(4)最后总结一下代码的执行流程:

同步=》事件循环【微任务,宏任务】=》微任务=》宏任务=》微任务......;

4,实现Jsonp

srcipt标签引入
iframe
后端
配置请求头

可能会有拓展问题:

同源策略:域名,协议,端口号相同。

为什么限制:为了防止恶意ajax请求,修改DOM页面,随意获取cookie隐私数据等;

域名:www.baidu.com

端口:8080,3000

协议:https/http

5,JS作用域

全局作用域:代码在程序的任何地方都能被访问,window对象的内置属性都拥有全局作用域;
函数作用域:只有在固定的代码片段才能被访问;
好处:隔离变量,不同作用域下同名变量不会有冲突。
作用域链:一般情况下,变量取值会在创建这个变量的函数作用域中取值,如果没找到,就会像上级作用域查找,知道查到全局作用域,这个过程就叫作用域链;

除了函数外,js没有块级作用域;
比如:在函数里定义一个变量,在函数外打印这个变量,即使函数调用了,也没办法打印;

如果有笔试题,或者现场出题的话,这个就是高频题了,就需要重点理解了。

注意:
(1)声明的变量是用var还是没有写(就是window);
(2)js变量提升的机制【变量悬挂声明】;
(3)注意本层作用域有没有变量提升;

6,JS作用域+this指向+原型笔试题(高频)

构造函数:this指向的是实例对象,
全局函数:this指向window;
use strict:指向的是undefined;
绑定事件:this指的是被绑定事件的元素;
箭头函数:this和父级this指向相同;

考题一:

function foo(){
    getName = function(){
        console.log(1)
    };
    return this;
}
Foo.getName = function(){
    console.log(2)
}
Foo.prototype.getName = function(){
    console.log(3)
}
var getName = function(){
    console.log(4)
}
function getName(){
    console.log(5)
}
Foo.getName() //2
getName() //4
Foo().getName() //1
getName() //1
new Foo().getName() //3

考题二:

var o = {
    a:10,
    b:{
        fn:function(){
            console.log(this.a)
            console.log(this)
        }    
    }
}
o.b.fn() //undefined b函数

考题三:

window.name = 'ByteDance'
function A(){
    this.name = 123;
}
A.prototype.getA = function(){
    console.log(this);
    return this.name+1;
}
let a = new A()
let funcA = function(){
    console.log(this)
    return this.name+1
}
funcA() //this指的是window; 'ByteDance1'

7, JS判断变量是不是数组,有哪些方法?

(1) Array.isArray(arr)
(2) arr instanceof Array【可写可不写,因为有bug】
(3) Object.prototype.toString.call(arr)
(4) Array.prototype.isPrototype(arr)
(5) arr.constructor.toString()

8, slice是干嘛的,splice是否会改变原数组,二者区别?

slice:选择截取部分内容,参数可以一个,两个,也可以是负数,返回新数组;
splice:会改变原数组;插入、删除、替换;返回删除的元素;

9,JS数组去重?

扩展运算符 + new set
循环
sort排序
代码演示:

(1) new Set
//Array.from(new set(arr))
//...new Set(arr)
var arr1 = [1,2,3,2,4,1];
function unique(arr){
    return [...new Set(arr)]
}
console.log(unique(arr1))

(2) 循环
function unique3(arr){
    var brr = []
    for(var i=0; i<arr.length; i++){
      if(brr.indexOf(arr[i]) == -1){
        brr.push(arr[i])
      }
    }
    return brr
  }
  console.log(unique3(arr));

(3) sort
function unique4(arr){
    arr = arr.sort()
    var brr = []
    for(var i=0;i<arr.length;i++){
      if(arr[1] !== arr[i-1]){
        brr.push(arr[i])
      }
    }
    return brr
  }
  console.log(unique4(arr));

10,找出多维数组的最大值?

Math.max(..item)
代码演示:

var arr = [
    [1,3,7,9],
    [22,77,90,78],
    [123,567,890,345],
    [1002,3004,3009,2890]
  ]
  function fn(arr){
    var brr = []
    arr.forEach((item,index)=>{
      brr.push(Math.max(...item))
    })
    return brr
  }
  console.log(fn(arr));

11,给字符串新增方法实现功能?

addPrefix() 添加前缀
代码演示:

 String.prototype.addPrefix = function (str){
    return str + this
  }
  console.log('Bryan'.addPrefix('Kobe'));

12,找出一个字符串出现次数最多的字符,出现了多少次?

var str = 'aaaabbbbssssxcccddddsdfscxxasa'
  var obj = {};
  for(var i=0; i<str.length; i++){
    var char = str.charAt(i)
    if(obj[char]){
      obj[char]++;
    }else{
      obj[char] = 1;
    }
  }
  console.log(obj);
  //统计出来最大值
  var max = 0;
  for(var key in obj){
    if(max < obj[key]){
      max = obj[key]
    }
  }
  //拿最大值去对比
  for(var key in obj){
    if(obj[key] == max){
      console.log('最多的字符是'+key);
      console.log('出现的次数是'+max);
    }
  }

13,字符串的方法?

字符串方法总结
方法功能
charAt()返回指定索引位置的字符
indexOf()返回字符串中检索指定字符第一次出现的位置
replace()替换与正则表达式匹配的字串
slice()提取字符串的片段,返回新的字符串
split()把字符串分隔为子字符串数组
substr()从起始索引号提取字符串中指定数目的字符
subString()提取字符串中两个指定的索引号之间的字符
toLowerCase()变小写
toUpperCase()变大写
toString()返回字符串对象值
trim()移除字符串首尾空白
valueOf()返回某个字符串对象的原始值

14,new操作符具体做了什么?

(1)创建了一个空对象;

(2)将空对象的原型,指向于构造函数的原型;

(3)将构造函数里的this指向空对象;

(4)将构造函数的属性和方法赋值给空对象;

(5)返回这个对象;

代码演示:

function Fun( name, age ){
    this.name = name;
    this.age = age;
  }
  function create( fn,...args ){
    //创建一个新对象
    var obj = {}
    //把对象的原型指向构造函数的原型
    Object.setPrototypeOf(obj,fn.prototype)
    //改变this的指向
    var result = fn.apply(obj.args)
    //对构造函数有返回值的处理判断
    return result instanceof Object ? result : obj;
  }
  console.log(create(Fun,18,'张三'));

15,闭包是什么?

(1)是什么:

闭包是一个函数内返回一个函数,外部函数能读取内部函数的变量;

(2)优点:

内部可以读取外部函数的变量,可以封装独有的方法和属性,保持内部的独立性;

(3)可以解决问题:

  • 比如说要点击ul下面的每一项li,点击每项弹出每项的内容;

代码演示:

var lis = document.querySelectorAll('li');
  for(var i=0; i<lis.length; i++){
    (function(i){
      lis[i].onclick = function(){
        alert(i)
      }
      lis[i]=null
    })(i)
  }
  • 防抖,柯里化,定时器等

(4)缺点:

变量会驻留在内存中,造成内存损耗问题;内存泄漏(IE)
解决:把闭包的函数设置为null;

16,原型链是什么?

这个问题经常会问,但千篇一律肯定没办法加分,所以需要说清楚,为什么会有原型,为什么会有原型链,在什么场景会用,方法是什么,原理是什么,按照这样的思路去说基本没问题。

我们知道创建对象的方式有三种,字面量,new,构造函数。前两个只能创建单个的对象,但构造函数可以把不同对象的共同属性和方法抽离出来,通过new实例化创建拥有共同属性和方法的不同对象。但是想一想,会出现什么问题呢?

由于js存储数据的方式是引用数据类型存储在堆里面,我们new出来的不同对象都拥有这一个相同的方法,但是都要在堆里面去申请不同的空间去存储,这样就会极大的浪费我们的内容空间。

所以这个时候我们就有了原型prototype。这样不同的对象可以通过原型去查找,节省内存空间,how nice!

也就是说原型可以解决:对象共享属性和共享方法。

总结简洁版(思路如下,自己整理能说明白即可):

(1)原型可以解决什么问题:对象共享属性和共享方法;
(2)谁有原型?函数拥有prototype,对象拥有:`__proto__`
(3)对象查找属性或者方法的顺序:

先在对象本身查找-->构造函数中查找-->对象的原型-->构造函数的原型中-->当前原型的原型;
(4)原型链的最顶端是Null;

17,js的继承有哪些方式?

不需要每个都讲,只需要讲到重点常用的几个即可;

(1)es6

class father{
    constructor(){
        this.age = 18
    }
}
class child extends father{
    constructor(){
        super()
        this.name = '张三'
    }
}
let Child = new child()
console.log(Child,Child.name,Child.age)

(2)原型链继承

child.prototype = new parent()
let Child = new child()
console.log(Child, Child.name, Child.age)
//继承的属性和方法在原型上

(3)构造函数继承:

function Father(){
    this.name = '张三'
  }
  function Son(){
    this.age = 20
    Father.call(this)
  }
  let o3 = new Son()
  console.log(o3);

(4)组合继承

function Father(name,age){
    this.name = name
    this.age = age
  }
  function Son(name,age){
    Father.call(this,name,age)
  }
  const son = new Son('刘德华', 20)
  console.log(son);

18,call、apply、bind的区别?

(1)相同点:功能一致,都可以改变this指向;

(2)语法:函数.call(fn,name),函数.apply(fn,[...args]),函数.bind();

(3)区别:

  • call,apply可以立即指向,bind不会立即指向,因为bind返回的是一个函数需要加入()执行;
  • 参数不同,apply第二个参数是数组,call和bind有多个参数需要挨个写;

(4)使用场景:

  • 找出最大值:
var arr = [1,2,4,5,78,21]
console.log(Math.max.apply(null,arr1))
  • 用bind的情况
var btn = document.querySelector('btn')
var h1s = document.querySelector('h1s')
btn.onclick = function(){
    console.log(this.id)
}.bind(h1s) //h1s

19,sort原理了解吗?

之前的版本是插入和快排,现在是冒泡;

使用场景:

(1)升序:

var arr = [1,2,23,21,222,778]
let res = arr.sort(function(a,b){
    return a-b
})
console.log(res)

(2)降序:

var arr = [1,2,23,21,222,778]
let res = arr.sort(function(a,b){
    return b-a
})
console.log(res)

(3)排序对象中某属性值的顺序:

var obj = [
    {name:'张三',age:2},
    {name:'李四',age:29},
    {name:'王五',age:70}
]
function compare(age){
    return function (a,b){
        var val1 = a[age]
        var val2 = b[age]
        return val1-val2
    }
}
var arr = obj.sort(compare('age'))
console.log(arr)

20,深浅拷贝的理解?(高频)

首先什么情况会出现深浅拷贝呢?

假设我们有一个已经定义了属性和方法的对象,我们再创建一个新对象,然后用=号把已定义的对象赋值给新创建的空对象;

如果我们修改新对象里的某个属性值,打印原来已定义的对象时,你会发现它的值也发生变化了;

这种情况就是发生了浅拷贝。为什么呢?

这是因为js的数据存储方式是基本数据类型再栈里面,引用数据类型在堆里面,但引用数据的指针,也就是房间号在栈里面存着。如果我们用=号去赋值,就相当于把指针赋值给了另一个对象,也就是说这两个对象的指针都是一样的,所以你修改了任意一个对象的值另一个也会发生变化。

那如果不想出现上面的情况,我们就需要进行深拷贝了,也就是说要在堆里面重新申请一个新空间,指针也是不同的,这样就不会发生连锁反应了。

简洁版思路:

共同点:复制

(1)浅拷贝:只复制引用地址,而未复制真正的值;object.assign()

(2)深拷贝:是复制真正的值(不同的引用)

(3)方式:JSON.parse(JSON.Stringify) 递归形式;

21,本地存储方式的区别?

(1)数据存放有效期:

localStorage:始终有效,持久存储;

sessionStorage:浏览器关闭就没了;

cookie:只在设置的cookie过期时间之前有效,即使窗口或者浏览器关闭也有效,其他不可以设置时间;

(2)存储大小的限制:

cookie存储量不能超过4k;
localStorage、sessionStorage不能超过5M;

22,find和filter区别?

(1)区别一:filter返回新数组,find返回具体的内容;

(2)区别二:find匹配到第一个即返回,filter只要满足条件push到新数组里,返回新数组;

23,some和every区别?

some是只要满足一个条件就返回true;

every是都满足返回为true;

24,合并多个对象?

(1)方法一:Object.assign(a,b)

(2)方法二:obj = {...a,...b}

(3)方法三:循环

function extend(target,source){
    for(var obj in source){
        target[obj] = source[obj]
    }
    return target
}
console.log(extend(a,b))

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐