面试之道

193 阅读6分钟
  • 这篇文章将介绍一些常问的js面试题

对象类型和原始类型不同之处是什么? 函数参数是对象会发生什么?

原始数据类型

  • Boolean
  • null
  • String
  • undefined
  • Number
  • Symbol(es6新增的)

引用数据类型

  • Object

  • Array

  • Function

    let bar = {}
    const foo = []
    const bar = foo // 复制了foo的变量地址(指针)
    bar.push(1)
    console.log(bar, foo)// 1, 1  指向同一个地址
    function test(persion) {  
    persion.age = 26  persion = { name: 'wn', age: 30 }  return persion}
    const p1 = {  name: '蜗牛',  age: 25}
    const p2 = test(p1)console.log(p2) // {name: 'wn', age: 30}
    console.log(p1) // {name: '蜗牛', age: 26}
    

- 原始类型存储的是值, 对象类型存储的是地址(指针),计算机会在内存中帮我们开辟一个空间来存放值,当我们需要去取这个空间的时候,这个空间会拥有一个指针,基本数据类型存放在栈中,基本数据类型值不可变。

  • 首先, 函数传参是传递对象指针的副本
  • test的这个参数person,此时是一个镜像p1,也就是说与p1有一份一模一样的指针
  • 当我们从新为person分配一个对象时,出现分歧
  • 重新赋值,导致persion拥有一个新的地址(指针),也就和p1再无瓜葛

 typeof 是否能正确判断数据类型? instanceof 能正确判断对象的原理?

typeof 1 // number
typeof '1' // string
typeof undefined // 'undefined'
typeof Boolean // 'boolean'
typeof Symbol // 'symbollet a = Symbol(1)
typeof [] // 'object'
typeof {} // 'object'
typeof console.log() // 'function'
**typeof对于原始数据类型来说,除了null,其他的都可以正确显示类型, typeof 对于对象来说,除了函数,都会显示object。**

 实现 instanceof 原理

instanceof是通过原型链来判断的

首先要了解

let a = []
console.log(a.__proto__ == Arrar.prototype)// true
所以只要判断是否a.__proto__ == Arrar.prototype// true
let arr = new Array() // Array
console.log(arr.__proto__) // [] // prototype
console.log(arr.__proto__.__proto__) // object
console.log(arr.__proto__.__proto__.__proto__) // null
Object.prototype 包含 arr.__proto__.__proto__

实现 instanceof 代码

function _instanceof(left, right) {
let leftValue = left.__proto__
let rightVlaue = rigth.prototype
    while(true) {
        if(leftValue === null) {            return false        }        if(leftValue === rigthVlaue) {
            return true
        }
        leftValue = leftValue.__ptoto__ // 再往上找
    }
}
**要明确知道,在JS类型转换只有三种情况,分别是:le**
1\. 转换为布尔值
2\. 转换为数字
3\. 转换为字符窜

借用大佬总结的转换表www.cnblogs.com/jc2182/p/11…

转Boolean的情况,除了 undefined, null, false, NaN, '',0, -0,其他所有的值都为true

**对象在转换类型时,回调用内置的[[ToPrimitive]]函数,对于该函数来说,转换类型的算法逻辑如下:**
  1. 如果已经是原始类型,那就不用转换
  2. 调用 x.valueOf(), 如果转换为基础类型,就返回转换的值
  3. 调用 x.toString(), 如果转换为基础类型,就返回转换的值
  4. 如果都没有返回原始类型 就会报错
一道有意思的题: 'a' + + 'b' => 'a' +  (+'b') // aNaN

什么是浅拷贝,如何实现浅拷贝,什么是深拷贝,如何实现深拷贝?

你首先要了解不同的基础数据类型的存值和引用数据类型的存值有什么不同

浅拷贝只复制一层对象的属性,并不包括对象里面的为引用类型的数据

// ...浅拷贝 拷贝就是重新应用一个地址,把属性拷贝过来,互不干扰,但不包括引用类型let a = {
 age: 18,
 arr: [1, 2, [3]]
}
 function copy(args) {
  let newobj = {}
  for(let key in a) {
   if(a.hasOwnProperty(key)) {    newobj[key] = a[key]
   }
  }
  return newobj
}
let b = copy(a)
b.age = 20
b.arr[0] = 0
console.log(a)// {arg: 18, arr: [0, 2, [3]]}
console.log(b)// {arg: 20, arr: [0, 2, [3]]}

我们这里讲讲深拷贝的实现:

**JSON.parse(JSON.stringify(Object)) 局限性:**
  • 会忽略 undefined
  • 会忽略 Symbol
  • 不能序列化函数
  • 不能解决循环引用对象

实现深拷贝 (递归拷贝)

function deepClone(args) {  let copy = args instanceof Array ? [] : {}  for(let key in args) {      if(args.hasOwnProperty(key)) {        copy[key] = typeof args[key] === 'object' ? deepClone(args[key]) : args[key]      }  }  return copy}let test = {  a: 1,  b: {    c: 2,    d: 3  },}let res = deepClone(test)test.a = 10
test.b.c = 6console.log(res)

这只是个简单的深拷的实现

什么是闭包?

**闭包的定义: 函数 A 内部有一个函数 B , 函数 B 可以访问到函数 A 中的变量,那么函数B就是闭包 。**
在JS中,闭包存在的意义是让我们可以间接的访问函数内部的变量

常见的就是循环中,使用闭包解决问题 'var' 定义函数问题

2种方式:

闭包会保留词法作用域,会造成内存泄漏
1\. 闭包
for (var i = 1; i <= 5; i++) {  (function(j) {    setTimeout(function timer(){      console.log(j)    }, j * 1000)  })(i)}
2\. 利用setTimeout的第三个参数
for(var i = 0; i <= 5; i++) {  setTimeout(    function timer(j) {      console.log(j)    },    i*1000,    i  )}

还有一种就是直接使用let
for(let i = 1; i <= 5; i++) {  setTimeout(function time(){    console.log(i)  }, i * 1000)}

手写Promise

手写promise要知道promise的三个状态:

'peding'  等待

**'resolved' 完成**

'rejected' 失败

模板

new Promise((resolve, reject) => {  ajax(url, (res) => {    if(res) {      resolve(res)    }else {      reject(res)    }  }).then((result) => {})})

先来看看实现promise需要哪些:

一个resolve()方法,一个reject()方法,一个.then()方法

// 先定义三个状态
const PEDING = 'peding'
const RESOLVED = 'resolved'
const REJECTED = 'rejected'

function MyPromise(fn) {
    const that = this
    that.value = null
    that.state = PEDING // 定义状态
    that.resolvedCallbacks = []
    that.rejectedCallbacks = []

    // 接下来完善resolve和reject函数
    function resolve(value) { // new promise.resolve(1).then(e)      if(that.state === PENDING) {        that.state = RESOLVED // 执行reslove修改状态        that.value = value        that.resolvedCallbacks.map(cb => cb(that.value))      }    }

    function reject(value) {      if(that.state === PENDING) {        that.state = REJECTED // 执行reject修改状态        that.value = value        that.rejectedCallbacks.map(cb => cb(that.value))      }    }   // 完善执行fn
   try {
     fn(resolve, reject)
   }catch (error){
     reject(error)
   }}
// 实现.then把他挂载到原型链上
MyPromise.prototype.then = function(onFulfilled, onRejected) {  const that = this  // 首先判断两个参数是否为参数类型,因为这两个参数是可选的  // 当参数不是函数类型的时候,我们手动创建一个箭头函数赋值给队形的参数  onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v // 透传  onRejected = typeof onRejected === 'function' ? onRejected : r => { throw r}  if(that.state === PENDING) {    that.resolvedCallbacks.push(onFulfilled)    that.rejectedCallbacks.push(onRejected)  }  if(that.state === RESOLVED) {    onFulfilled(that.value)  }  if(that.state === REJECTED) {    onRejected(that.value)  }}

// 测试var test = new MyPromise((resolve, reject) => {  setTimeout(() => {    resolve(1)    console.log(1)  }, 1000)})function test2() {  return new MyPromise((resolve, reject) => {    setTimeout(() => {      console.log(2)      resolve(2)    }, 500)  })}test.then(test2)// 1, 2

这只是一个简单版的promise,还有许多地方需要优化,比如.then就相当于new了一个promise,能拿到上一个promise的值,还是需要对回调结果做一些处理等等。

js进阶 call,bind,apply,new的实现

例子:
let foo = {  name: 'wn'  // fn: function bar() {} }function bar() {  console.log(this.name)// 'wn'}bar.call(foo)

通过代码可以看调用了call方法把this指向foo所以能拿到name:wn

所以call的实现原理:

Function.prototype.Mycall = function(context) {
    if(typeof this !== 'function') {// 判断是不是函数调用call
        throw new Error('error')
    }
    context= context || window // 没有传参默认指向window    // 绑定this
    // console.log(this) // foo
    context.fn = this //相当于例子里面的这一步 fn: function bar() {}这样就可以访问到foo的name属性了

    // 参数处理
    const args = [...arguments].slice(1)    const result = context.fn(...args)    return result
}

apply的实现:

apply处理参数 和 call 有区别

// apply 只接受两个参数,fn, Arraylet foo = {  name: 'wn'}function bar(x) {  console.log(this.name)  console.log(x)}bar.apply(foo, [1]) // bar.call(foo, 1, 2, 3)Function.prototype.myApply = function(context) {  // this ==> bar this指向调用调用myApply的函数  if(typeof this !== 'function') {    throw new Error('error')  }  context = context || window // 不传值默认为window  context.fn = this  let result  // 处理参数 和 call 有区别  if(arguments[1]) {    result = context.fn(...arguments[1])  }else {    result = context.fn()  }  delete context.fn  return result}

bind的实现:

bind与call不同的就是bind返回一个函数体

Function.prototype.myBind = function(context) {  const _this = this  const args = [...arguments].slice(1)  // 返回一个函数  return function F() {    // 因为返回了一个函数,我们可以 new F(), 所以需要判断    if(this instanceof F) {      return new _this(...args, ...arguments)    }    return _this.apply(context, args.concat(...arguments))  }}

new的实现原理:

使用new:

  1. 新生成一个对象
  2. 链接到原型
  3. 绑定this
  4. 返回新对象
function Persion(name) {  this.name = name}// var wn = new Persion('123')var wn = create(Persion, '123')console.log(wn) // wn instanceof Persion  // wn.__proto__ === Persion.prototype// 1\. 新生成一个对象// 2\. 链接到原型// 3\. 绑定this// 4\. 返回新对象function create() {  let obj = {}  let Con = [].shift.call(arguments) // arguments ===> [Persion, '蜗牛']  obj.__proto__ === Con.prototype  let result = Con.apply(obj, arguments)  return result instanceof Object ?  result : obj}

以上希望对各位有所帮助

一波自推

小编正在找工作,没有什么工作经验,如果需要前端的话留言或者联系我1362368453@qq.com