前端常见面试题-js-es6

JS

延迟加载js

<script src="" defer> </script>
<script src="" async> </script>

defer 异步并行下载,在document解析完后执行,多脚本按顺序执行,
async 异步并行下载,只要下载完就执行,不分顺序。

输出结果

console.log(typeof NaN)
console.log(typeof undefined)
console.log(typeof null)

number , undefined obj
null 是特殊的object 类型

输出结果

console.log(true + 1)
console.log('jason' + true)
console.log(undefined + 1)

2 , jasontrue , NaN
基本上处理字符串优先转化,剩下的都是Number转化,
Number(true) = 1
Number(undefined) 结果为NaN ,NaN+什么数字都是NaN

输出结果

var bar = 1 
function test() {
  console.log(bar)
  var bar = 2;
  console.log(bar);
}

undefined , 2
在test方法里,定义的var bar = 2 被变量提升了。

输出结果

for(var i= 0; i<3; i++) {
  setTimeout(function () {
          console.log(i)
  },1000)
}

3,3,3 间隔为1秒
因为访问的是for 里定义的var i 所有定时起

输出结果

  var foo = function() {
    console.log(1)
  }
function foo() {
    console.log(2)
}
foo()

1
声明的优先

输出结果

  function c() {
    var b = 1;
     function a() {
          console.log(b)
          var b = 2 
          console.log(b)
    }
    console.log(b)
  } 
c()

undefined , 2 ,1

输出结果

var name = 'aaa'
(function(){
      if(typeof name == 'undefined') {
              var name = 'java'
              consolo.log("111",name)
      }else {
              consolo.log("222",name)
      }
})()

输出:111java
匿名函数是一个块作用域的模拟,if没有作用域,但是下面有定义了var,所以变量只能提升到匿名函数内,name为undefined

输出结果

var f = true 
if( f === true) {
   var a = 10;
}
function fn() {
    var b = 20;
    c = 30;
}
fn()
console.log(a)
console.log(c)
console.log(b)

10, 20,报错
var 没有块概念,所以a全局能拿到,
c是直接绑定在window上,所以也能拿到
b由于是定义在方法,外部无法找到,直接找不到定义,报错

输出结果

var a = {}
var b = { key : 'a'}
var c = {key:'c'}
a[b] = '111'
a[c] = '222'
console.log(a[b])

222
注意这里的a[b]里面的b 对象会被转化成字符串'[object Object]' 。
同理 a[c] 里的c 也会被转化成 '[object Object]'
所以 结果是 a['[object Object]'] = '111' ; a['[object Object]'] = '222'
所有最后等于222

js数组去除重复的方法

//方法1  new Set
  function unique(array) {
       let set = new Set(array)
       return Array.form(set)
   }
//方法2 遍历 indexOf
    function unique(array) {
        let lastList = []
       array.foreach( (item) => {
            if(lastList.indexOf(item) < 0) {
              lastList.push(item)
           } 
       })
       return lastList
   }
//方法3 递归删除法
function unique(array) {
   array = array.sort()
   var len = array.length
   function loop(index) {
    if(index >= 1) {
        if(array[index] === array[index - 1 ]) {
            array.splice(index,1)
            }
            loop(index - 1)
        } 
   }
   loop(len - 1)
   return array
}
console.log(unique2([3,4,4,1,2,4,1,100,3])) 

//方法4 两数组对比法
function unique3(array) {
    array = array.sort()
    let lastList = [array[0]] //初始化 就保存第一元素 然后逐一跟数组对比,有重复 
    for(var i = 0; i < array.length; i++) { 
        if(lastList[lastList.length - 1] !== array[i]) {//如果当前last里面没有则加入
            lastList.push(array[i]) 
        }
    }
    return lastList
 }
 console.log(unique3([3,4,4,1,2,4,1,100,3])) 

给字符串添加addPre方法,实现 "job".addPre("good") 输出 good job

通过原型链的方法实现

String.prototype.addPre = function(str) {
  return str + this
}

把嵌套的二维数组转化成一维数组

let a =
[
[4,5,6,7],
[7,8,9],
[1,2,3],
]
输出
[4,5,6,7,7,8,9,1,2,3]
//方法1 
 function arr2ToArr(arr) {
     let newList = [] 
    arr.forEach(item => { 
        newList = newList.concat(item)
    })
    return newList
 }
console.log(arr2ToArr(a))

把嵌套的二维数组 里面最大的取出,组成新数组

let a =
[
[4,5,6,7],
[7,8,9],
[1,2,3],
]
输出
[7,9,3] 

 function arr2ToMaxArr(arr) {
     let newList = [] 
    arr.forEach(item => { 
        newList = newList.push(Math.max.apply(null,item)) //这里apply可以解构数组为动态参数
    })
    return newList
 }
console.log(arr2ToMaxArr(a))

闭包

定义:一个函数可以调用另外一个函数的作用域中的变量
函数内还有函数,两个函数作用域相连,形成闭包
形成条件:函数嵌套,内部函数调用外部函数变量

for(var i= 0; i<3; i++) {
  (function (i){
       setTimeout(function () {
          console.log(i)
      },1000)
  })(i) 
}

能够正常输出 1,2,3 间隔1秒,因为形成闭包,i 变量被保留下来。

call apply bind区别

三个都是改变执行的上下文对象,可以实现继承
call和apply都是立即调用
call(obj,parm1,parm2) 第二个参数可以是 动态参数,适合解构
apply(obj,arr) 第二个参数必须是数组
bind 和call使用类似,只提前绑定,返回新函数,但不立即调用方法。

js判断变量是否为数组

// 
var arr = []
arr instanceof Array
// true

var arr = []
Object.prototype.toString.call(arr)
//"[object Array]"

var arr = []
Array.prototype.isPrototypeOf(arr)
//true

var arr = []
arr.constructor.toString()
//"function Array() { [native code] }"

var arr = []
Array.isArray(arr)
//true

new操作做了什么事情

  1. 创建一个空对象
  2. 将this 指向这个对象
  3. 通过this给对象添加属性
  4. 通过this,将proto属性指向构造函数的原型对象
/*
  create函数要接受不定量的参数,第一个参数是构造函数(也就是new操作符的目标函数),其余参数被构造函数使用。
  new Create() 是一种js语法糖。我们可以用函数调用的方式模拟实现
*/
function create(Con,...args){
    //1、创建一个空的对象
    let obj = {}; // let obj = Object.create({});
    //2、将空对象的原型prototype指向构造函数的原型
    Object.setPrototypeOf(obj,Con.prototype); // obj.__proto__ = Con.prototype
    //3、改变构造函数的上下文(this),并将剩余的参数传入
    let result = Con.apply(obj,args);
    //4、在构造函数有返回值的情况进行判断
    return result instanceof Object?result:obj;
}

proto和constructor属性是对象所独有的;
② prototype属性是函数所独有的。但是由于JS中函数也是一种对象,所以函数也拥有proto和constructor属性。

proto属性都是由一个对象指向一个对象,即指向它们的原型对象(也可以理解为父对象),
它的作用就是当访问一个对象的属性时,如果该对象内部不存在这个属性,那么就会去它的proto属性所指向的那个对象(可以理解为父对象)里找,如果父对象也不存在这个属性,则继续往父对象的proto属性所指向的那个对象里找,如果还没找到,则继续往上找…直到原型链顶端null,再往上找就相当于在null上取值,会报错.
由以上这种通过proto属性来连接对象直到null的一条链即为我们所谓的原型链。

prototype属性它是函数所独有的,它是从一个函数指向一个对象。它的含义是函数的原型对象,
也就是这个函数(其实所有函数都可以作为构造函数)所创建的实例的原型对象,
由此可知:f1.proto === Foo.prototype,它们两个完全一样。
作用:让该函数所实例化的对象们都可以找到公用的属性和方法。任何函数在创建的时候,其实会默认同时创建该函数的prototype对象。

constructor属性的含义就是指向该对象的构造函数,所有函数(此时看成对象了)最终的构造函数都指向Function

因为创建对象的前提是需要有constructor,而这个constructor可能是对象自己本身显式定义的或者通过proto在原型链中找到的。而单从constructor这个属性来讲,只有prototype对象才有。每个函数在创建的时候,JS会同时创建一个该函数对应的prototype对象,而函数创建的对象.proto === 该函数.prototype,该函数.prototype.constructor===该函数本身,故通过函数创建的对象即使自己没有constructor属性,它也能通过proto找到对应的constructor,所以任何对象最终都可以找到其构造函数(null如果当成对象的话,将null除外)。

原型链

当访问对象属性时候,会先在对象本身属性找,没有则去proto隐式原型找,即构造函数的prototype ,没有继续在prototype的proto找,这样一层层查找形成链式。
总结:

  1. 一直往上找,直到null都没有,就返回undefined
  2. Object.prototype.proto === null

js继承判断

  1. 原型链继承
  2. 构造函数继承
  3. 实例继承
  4. 组合继承
  5. 寄生组合
  6. es6 extends

== 和 === 区别

== 是语法糖,会转化成对应的数据类型,再比较。
=== 是完全匹配

cookie 和 session区别

cookie 是保留在客户端,而且容量大小有限制,不安全,明文可以篡改。
session保留在服务端,访问多了,会响应服务端性能。通过客户端记录ID 在cookie 实现用户登陆态记录。

sort背后的原理

js自带的排序方法。
通过 unicode编码进行排序 ,默认是从小到大排序

slice和splice区别

slice 切割数组,返回切割后的内容,但原数组不改变
splice 可以删除和插入数据,替换,并返回删除的数据,原数组也会变化。

深度拷贝和浅拷贝区别

浅拷贝: 对于对象,只是复制对象的引用,修改属性,会导致两边修改。一般用Object.assign()
深度拷贝:完全复制对象的每一个属性,每一个值,并且嵌套对象依然是完全拷贝。并且被复制对象与拷贝对象无关联。修改不影响。
通过字符串转化再转对象 JSON.stringify JSON.parse可以实现。但是如果拷贝对象包含正则表达式,函数,或者undefined等值,此方法就会出现问题
自己实现递归遍历每一个属性

ES6

1.var,let,const 区别

var

  1. 存在变量提升
  2. 可以重复定义同一个var 变量名
  3. var 在全局定义,会直接添加到window

let

  1. 不存在变量提升
  2. 不能重复定义一样的变量名
  3. 不会添加到window
  4. 存在块作用域
  5. 有暂时性死区
var temp = 0
if(true){
  temp = 10
  let temp ;//由于这里定义了let 所以在 if块内 let上面的代码都会变成死区
}

const

和 let 差不多,但是cost不能被修改

看看结果

function demo(){
  let n = 2
  if (true) {
    let n = 1 
  }
  console.log(n)//以当前块范围取值
}
demo()

//输出2

输出结果

const demo = (first,...num) => {
  return [first,num]
}
demo(1,2,3,4,5)

输出:[1,[2,3,4,5]]

合并对象方法

Object.assign()

  Object.assign(tragetObj,inputObj)

解构

  let newObj = {...a, ...b}

遍历属性赋值

 for (var key in souce) {
  targetObj[key] = souceObj[key]
}

promise的几个状态

有三个状态 pending(进行中) fulfilled rejected

箭头函数和普通函数区别

箭头:

  1. 是匿名函数,不能做构造函数,不能用new
  2. 没有arguments,使用的是rest参数
    rest 参数(形式为...变量名),用于获取函数的多余参数
const test = (...arg) => {
  console.log(arg[0])
}
test(1,2)
  1. 不绑定 this 获取其所在最近上下文的this使用
  2. 通过call apply 方法调用函数,只需传一个参数,对this没有影响
let obj = {
  name: "qianzhixiang",
  func: (a,b) => {
      console.log(this.name,a,b);
  }
};
obj.func(1,2); // 1 2
let func = obj.func;
func(1,2); //   1 2
let func_ = func.bind(obj);
func_(1,2);//  1 2
func(1,2);//   1 2
func.call(obj,1,2);// 1 2
func.apply(obj,[1,2]);//  1 2
  1. 箭头函数没有原型属性
  2. 函数不能做generator函数,不能使用yied

foreach map filter 区别

map返回新的数组对象
foreach和map都是遍历数组
filter 返回原数组的子集合,通过返回的ture/ false 判断添加返回的数组里。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容