ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

javaScript ES5常考面试题总结

2019-03-09 09:41:54  阅读:352  来源: 互联网

标签:面试题 obj 对象 常考 age ES5 let typeof 类型


js的六种原始值

  • boolean
  • null
  • undefined
  • number
  • string
  • symbol

坑1:
首先原始类型存储的都是值,是没有函数可以调用的,比如 undefined.toString() 会报错
但是 '1'.toString()是可以调用的,因为已经转换成了对应的对象类型了。

坑2:
number的类型 0.1 + 0.2 !== 0.3

坑3:
对于 null 来说,很多人会认为他是个对象类型,其实这是错误的。虽然 typeof null 会输出 object,但是这只是 JS 存在的一个悠久 Bug。在 JS 的最初版本中使用的是 32 位系统,为了性能考虑使用低位存储变量的类型信息,000 开头代表是对象,然而 null 表示为全零,所以将它错误的判断为 object 。虽然现在的内部类型判断代码已经改变了,但是对于这个 Bug 却是一直流传下来。

对象(Object)类型

在 JS 中,除了原始类型那么其他的都是对象类型了。对象类型和原始类型不同的是,原始类型存储的是值,对象类型存储的是地址(指针)。当你创建了一个对象类型的时候,计算机会在内存中帮我们开辟一个空间来存放值,但是我们需要找到这个空间,这个空间会拥有一个地址(指针)。

const a = []; // 对于常量 a 来说,假设内存地址(指针)为 #001,那么在地址 #001 的位置存放了值 [],常量 a 存放了地址(指针) #001

const b = 1;

b.push(a); // 当我们将变量赋值给另外一个变量时,复制的是原本变量的地址(指针),
也就是说当前变量 b 存放的地址(指针)也是 #001,
当我们进行数据修改的时候,就会修改存放在地址(指针) #001 上的值,也就导致了两个变量的值都发生了改变。

typeof vs instanceof

** 涉及面试题:typeof 是否能正确判断类型?instanceof 能正确判断对象的原理是什么?

typeof 对于原始类型来说,除了 null 都可以显示正确的类型

typeof 1 // 'number'
typeof '1' // 'string'
typeof undefined // 'undefined'
typeof true // 'boolean'
typeof Symbol() // 'symbol'

typeof 对于对象来说,除了函数都会显示 object,所以说 typeof 并不能准确判断变量到底是什么类型

typeof [] // 'object'
typeof {} // 'object'
typeof console.log // 'function

如果我们想判断一个对象的正确类型,这时候可以考虑使用 instanceof,因为内部机制是通过原型链来判断的

const Person = function() {}
const p1 = new Person()
p1 instanceof Person // true

var str = 'hello world'
str instanceof String // false

var str1 = new String('hello world')
str1 instanceof String // true

todo: 类型判断再去深入学习。

类型转换

首先我们要知道,在 JS 中类型转换只有三种情况,分别是:

转换为布尔值
转换为数字
转换为字符串

【对象转原始类型】
对象在转换类型的时候,会调用内置的 [[ToPrimitive]] 函数,对于该函数来说,算法逻辑一般来说如下:

如果已经是原始类型了,那就不需要转换了
调用 x.valueOf(),如果转换为基础类型,就返回转换的值
调用 x.toString(),如果转换为基础类型,就返回转换的值
如果都没有返回原始类型,就会报错

当然你也可以重写 Symbol.toPrimitive ,该方法在转原始类型时调用优先级最高。

let a = {
  valueOf() {
    return 0
  },
  toString() {
    return '1'
  },
  [Symbol.toPrimitive]() {
    return 2
  }
}
1 + a // => 3

四则运算符

加法运算符不同于其他几个运算符,它有以下几个特点:

运算中其中一方为字符串,那么就会把另一方也转换为字符串
如果一方不是字符串或者数字,那么会将它转换为数字或者字符串

1 + '1' // '11'
true + true // 2
4 + [1,2,3] // "41,2,3" 数组 -> 字符串 的转换

经典考题:

'a' + + 'b' // -> "aNaN"
因为 + 'b' 等于 NaN,所以结果为 "aNaN",你可能也会在一些代码中看到过 + '1' 的形式来快速获取 number 类型。

比较运算符

如果是对象,就通过 toPrimitive 转换对象
如果是字符串,就通过 unicode 字符索引来比较

let a = {
  valueOf() {
    return 0
  },
  toString() {
    return '1'
  }
}
a > -1 // true
// 在以上代码中,因为 a 是对象,所以会通过 valueOf 转换为原始类型再比较值。

this

1、谁调用this,this就是谁
2、箭头函数没有this,另外对箭头函数使用 bind 这类函数是无效的

function foo() {
  console.log(this.a)
}
var a = 1
foo() // 这里调用相当于 window.foo() 所以 this则是window 那么window.a = 1

const obj = {
  a: 2,
  foo: foo
}
obj.foo() // this = obj 所以 obj.a = 2 所以这里输出2

const c = new foo() // 对于 new 的方式来说,this 被永远绑定在了 c 上面,不会被任何方式改变 this

bind

原则:对于这些函数来说,this 取决于第一个参数,如果第一个参数为空,那么就是 window。

let a = {}
let fn = function () { console.log(this) }
fn.bind().bind(a)() // => 根据规则 打印出来的就是window

== vs ===

== 会进行类型转换
=== 不会进行类型转换

平时使用还是强烈建议使用===进行严格的判断

闭包

闭包的定义其实很简单:函数 A 内部有一个函数 B,函数 B 可以访问到函数 A 中的变量,那么函数 B 就是闭包。

function A() {
  let a = 1
  window.B = function () {
      console.log(a)
  }
}
A()
B() // 1

在 JS 中,闭包存在的意义就是让我们可以间接访问函数内部的变量。

通过闭包解决for循环的访问

for (var i = 1; i <= 5; i++) {
  ;(function(j) {
    setTimeout(function timer() {
      console.log(j)// 1 ,2 ,3 ,4 ,5
    }, j * 1000)
  })(i)
}

另外一个方案

for (let i = 1; i <= 5; i++) {
  setTimeout(function timer() {
    console.log(i) // 输出 1,2,3,4,5
  }, i * 1000)
}

上面代码中,变量i是let声明的,当前的i只在本轮循环有效,所以每一次循环的i其实都是一个新的变量,所以最后输出的是6。你可能会问,如果每一轮循环的变量i都是重新声明的,那它怎么知道上一轮循环的值,从而计算出本轮循环的值?这是因为 JavaScript 引擎内部会记住上一轮循环的值,初始化本轮的变量i时,就在上一轮循环的基础上进行计算。

深浅拷贝

之前,我们了解了对象类型在赋值的过程中其实是复制了地址,从而会导致改变了一方其他也都被改变的情况。通常在开发中我们不希望出现这样的问题,我们可以使用浅拷贝来解决这个情况。

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

【浅拷贝】
首先可以通过 Object.assign 来解决这个问题,很多人认为这个函数是用来深拷贝的。其实并不是,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

通常浅拷贝就能解决大部分问题了,但是当我们遇到如下情况就可能需要使用到深拷贝了

let a = {
  age: 1,
  jobs: {
    first: 'FE'
  }
}
let b = { ...a }
a.jobs.first = 'native'
console.log(b.jobs.first) // native

浅拷贝只解决了第一层的问题,如果接下去的值中还有对象的话,那么就又回到最开始的话题了,两者享有相同的地址。要解决这个问题,我们就得使用深拷贝了。

【深拷贝】
这个问题通常可以通过 JSON.parse(JSON.stringify(object)) 来解决

但是该方法也是有局限性的:

会忽略 undefined
会忽略 symbol
不能序列化函数
不能解决循环引用的对象

let a = {
  age: undefined,
  sex: Symbol('male'),
  jobs: function() {},
  name: 'yck'
}
let b = JSON.parse(JSON.stringify(a))
console.log(b) // {name: "yck"}

看输出结果忽略了undefined , symbol 和 函数

实现一个真正的深拷贝(简易版)

function deepClone(obj) {
    // 判断是不是对象
  function isObject(o) {
    return (typeof o === 'object' || typeof o === 'function') && o !== null
  }
    // 不是对象的话报错
  if (!isObject(obj)) {
    throw new Error('非对象')
  }
    
  let isArray = Array.isArray(obj)
  let newObj = isArray ? [...obj] : { ...obj } // 是数组的话 用数组的解构来实现对象的复制,对象也同理
// 静态方法 Reflect.ownKeys() 返回一个由目标对象自身的属性键组成的数组
  Reflect.ownKeys(newObj).forEach(key => {
    newObj[key] = isObject(obj[key]) ? deepClone(obj[key]) : obj[key] // 这里进行递归实现多层对象的复制
  })

  return newObj
}

原型

当我们创建一个对象时 let obj = { age: 25 },我们可以发现能使用很多种函数,但是我们明明没有定义过它们,对于这种情况你是否有过疑惑?

其实每个 JS 对象都有 proto 属性,这个属性指向了原型。这个属性在现在来说已经不推荐直接去使用它了,这只是浏览器在早期为了让我们访问到内部属性 [[prototype]] 来实现的一个东西。

其实原型链就是多个对象通过 proto 的方式连接了起来。为什么 obj 可以访问到 valueOf 函数,就是因为 obj 通过原型链找到了 valueOf 函数。

标签:面试题,obj,对象,常考,age,ES5,let,typeof,类型
来源: https://www.cnblogs.com/shiyou00/p/10493709.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有