前端面试js

74 阅读10分钟

1. javaScript有哪些数据类型

数据类型分别是Undefined, null, boolean, Number,String. Object, Symbol, Biglnt.

symbol代表创建后独一无二不可变的数据类性,它主要为了解决可能出现的全局变量冲突的问题。 BigInt是一种数字类型的数据,它可以表示任意进度格式的整数,使用Bigint可以安全的存储和操作大整数。

这些数据可以分为原始数据类型和引用数据类型:

  • 栈:原始数据类型(Undefined、Null、Boolean、Number、String)
  • 堆:引用数据类型(对象、数组和函数)

数据类型检测的方式有哪些
(1)typeof

JavaScript复制代码

console.log(typeof 2); // number

console.log(typeof true); // boolean

console.log(typeof 'str'); // string

console.log(typeof []); // object

console.log(typeof function(){}); // function

console.log(typeof {}); // object

console.log(typeof undefined); // undefined

console.log(typeof null); // object

其中数组、对象、null都会被判断为object,其他判断都正确。

(2)instanceof
instanceof可以正确判断对象的类型,其内部运行机制是判断在其原型链中能否找到该类型的原型。

JavaScript复制代码


console.log(2 instanceof Number); // false

console.log(true instanceof Boolean); // false

console.log('str' instanceof String); // false

console.log([] instanceof Array); // true

console.log(function(){} instanceof Function); // true

console.log({} instanceof Object); // true

可以看到,instanceof只能正确判断引用数据类型,而不能判断基本数据类型。instanceof 运算符可以用来测试一个对象在其原型链中是否存在一个构造函数的 prototype 属性。

(3) constructor

JavaScript复制代码

console.log((2).constructor === Number); // true

console.log((true).constructor === Boolean); // true

console.log(('str').constructor === String); // true

console.log(([]).constructor === Array); // true

console.log((function() {}).constructor === Function); // true

console.log(({}).constructor === Object); // true

constructor有两个作用,一是判断数据的类型,二是对象实例通过 constrcutor 对象访问它的构造函数。需要注意,如果创建一个对象来改变它的原型,constructor就不能用来判断数据类型了:

JavaScript复制代码


function Fn(){};

Fn.prototype = new Array();

var f = new Fn();

console.log(f.constructor===Fn); // false

判断数组的方式有哪些  
●通过Object.prototype.toString.call()做判断  


JavaScript复制代码



Object.prototype.toString.call(obj).slice(8,-1) === 'Array';

●通过原型链做判断  

JavaScript复制代码

obj.__proto__ === Array.prototype;

●通过ES6Array.isArray()做判断  


JavaScript复制代码

Array.isArrray(obj);

●通过instanceof做判断  


JavaScript复制代码


obj instanceof Array

●通过Array.prototype.isPrototypeOf  


JavaScript复制代码


Array.prototype.isPrototypeOf(obj)

console.log(f.constructor===Array); // true

null和undefined区别

首先 Undefined 和 Null 都是基本数据类型,这两个基本数据类型分别都只有一个值,就是 undefined 和 null。

undefined 代表的含义是未定义,null 代表的含义是空对象。一般变量声明了但还没有定义的时候会返回 undefined,null主要用于赋值给一些可能会返回对象的变量,作为初始化。

undefined 在 JavaScript 中不是一个保留字,这意味着可以使用 undefined 来作为一个变量名,但是这样的做法是非常危险的,它会影响对 undefined 值的判断。我们可以通过一些方法获得安全的 undefined 值,比如说 void 0。

当对这两种类型使用 typeof 进行判断时,Null 类型化会返回 “object”,这是一个历史遗留的问题。当使用双等号对两种类型的值进行比较时会返回 true,使用三个等号时会返回 false。

isNaN 和 Number.isNaN 函数的区别?

  • 函数 isNaN 接收参数后,会尝试将这个参数转换为数值,任何不能被转换为数值的的值都会返回 true,因此非数字值传入也会返回 true ,会影响 NaN 的判断。
  • 函数 Number.isNaN 会首先判断传入参数是否为数字,如果是数字再继续判断是否为 NaN ,不会进行数据类型的转换,这种方法对于 NaN 的判断更为准确。

== 操作符的强制类型转换规则?
对于 == 来说,如果对比双方的类型不一样,就会进行类型转换。假如对比 x 和 y 是否相同,就会进行如下判断流程:

  1. 首先会判断两者类型是否相同,相同的话就比较两者的大小;
  2. 类型不相同的话,就会进行类型转换;
  3. 会先判断是否在对比 null 和 undefined,是的话就会返回 true
  4. 判断两者类型是否为 string 和 number,是的话就会将字符串转换为 number

JavaScript复制代码

1 == '1'1 == 1

5判断其中一方是否为 boolean,是的话就会把 boolean 转为 number 再进行判断

JavaScript复制代码


'1' == true'1' == 11 == 1
  1. 判断其中一方是否为 object 且另一方为 string、number 或者 symbol,是的话就会把 object 转为原始类型再进行判断

JavaScript复制代码

'1' == { name: 'js' }

'1' == '[object Object]'

其他值到数字值的转换规则?

  • Undefined 类型的值转换为 NaN。
  • Null 类型的值转换为 0。
  • Boolean 类型的值,true 转换为 1,false 转换为 0。
  • String 类型的值转换如同使用 Number() 函数进行转换,如果包含非数字值则转换为 NaN,空字符串为 0。
  • Symbol 类型的值不能转换为数字,会报错。
  • 对象(包括数组)会首先被转换为相应的基本类型值,如果返回的是非数字的基本类型值,则再遵循以上规则将其强制转换为数字。

什么是 JavaScript 中的包装类型?

在 JavaScript 中,基本类型是没有属性和方法的,但是为了便于操作基本类型的值,在调用基本类型的属性或方法时 JavaScript 会在后台隐式地将基本类型的值转换为对象,如:

const a = "abc";
a.length; // 3
a.toUpperCase(); // "ABC"

在访问'abc'.length时,JavaScript 将'abc'在后台转换成String('abc'),然后再访问其length属性。

JavaScript也可以使用Object函数显式地将基本类型转换为包装类型

var a = 'abc'
Object(a) // String {"abc"}

object.assign和扩展运算法是深拷贝还是浅拷贝,两者区别

let outObj = {
  inObj: {a: 1, b: 2}
}
let newObj = {...outObj}
newObj.inObj.a = 2
console.log(outObj) // {inObj: {a: 2, b: 2}}

Object.assign():

let outObj = {
  inObj: {a: 1, b: 2}
}
let newObj = Object.assign({}, outObj)
newObj.inObj.a = 2
console.log(outObj) // {inObj: {a: 2, b: 2}}

可以看到,两者都是浅拷贝。

  • Object.assign()方法接收的第一个参数作为目标对象,后面的所有参数作为源对象。然后把所有的源对象合并到目标对象中。它会修改了一个对象,因此会触发 ES6 setter。
  • 扩展操作符(…)使用它时,数组或对象中的每一个值都会被拷贝到一个新的数组或对象中。它不复制继承的属性或类的属性,但是它会复制ES6的 symbols 属性。

如何判断一个对象是空对象

  • 使用JSON自带的.stringify方法来判断:
if(Json.stringify(Obj) == '{}' ){
    console.log('空对象');
}
  • 使用ES6新增的方法Object.keys()来判断:

if(Object.keys(Obj).length < 0){ console.log('空对象'); }

Proxy 可以实现什么功能?

在 Vue3.0 中通过 Proxy 来替换原本的 Object.defineProperty 来实现数据响应式。

Proxy 是 ES6 中新增的功能,它可以用来自定义对象中的操作。

let p = new Proxy(target, handler)

target 代表需要添加代理的对象,handler 用来自定义对象中的操作,比如可以用来自定义 set 或者 get 函数。

下面来通过 Proxy 来实现一个数据响应式:

let onWatch = (obj, setBind, getLogger) => {
  let handler = {
    get(target, property, receiver) {
      getLogger(target, property)
      return Reflect.get(target, property, receiver)
    },
    set(target, property, value, receiver) {
      setBind(value, property)
      return Reflect.set(target, property, value)
    }
  }
  return new Proxy(obj, handler)
}
let obj = { a: 1 }
let p = onWatch(
  obj,
  (v, property) => {
    console.log(`监听到属性${property}改变为${v}`)
  },
  (target, property) => {
    console.log(`'${property}' = ${target[property]}`)
  }
)
p.a = 2 // 监听到属性a改变
p.a // 'a' = 2

当然这是简单版的响应式实现,如果需要实现一个 Vue 中的响应式,需要在 get 中收集依赖,在 set 派发更新,之所以 Vue3.0 要使用 Proxy 替换原本的 API 原因在于 Proxy 无需一层层递归为每个属性添加代理,一次即可完成以上操作,性能上更好,并且原本的实现有一些数据更新不能监听到,但是 Proxy 可以完美监听到任何方式的数据改变,唯一缺陷就是浏览器的兼容性不好。

map和Object的区别

MapObject
意外的键Map默认情况不包含任何键,只包含显式插入的键。Object 有一个原型, 原型链上的键名有可能和自己在对象上的设置的键名产生冲突。
键的类型Map的键可以是任意值,包括函数、对象或任意基本类型。Object 的键必须是 String 或是Symbol。
键的顺序Map 中的 key 是有序的。因此,当迭代的时候, Map 对象以插入的顺序返回键值。Object 的键是无序的
SizeMap 的键值对个数可以轻易地通过size 属性获取Object 的键值对个数只能手动计算
迭代Map 是 iterable 的,所以可以直接被迭代。迭代Object需要以某种方式获取它的键然后才能迭代。
性能在频繁增删键值对的场景下表现更好。在频繁添加和删除键值对的场景下未作出优化。

map和weakMap的区别

(1)Map

map本质上就是键值对的集合,但是普通的Object中的键值对中的键只能是字符串。而ES6提供的Map数据结构类似于对象,但是它的键不限制范围,可以是任意类型,是一种更加完善的Hash结构。如果Map的键是一个原始数据类型,只要两个键严格相同,就视为是同一个键。

实际上Map是一个数组,它的每一个数据也都是一个数组,其形式如下:

const map = [
     ["name","张三"],
     ["age",18],
]

Map数据结构有以下操作方法:

  • sizemap.size 返回Map结构的成员总数。

  • set(key,value) :设置键名key对应的键值value,然后返回整个Map结构,如果key已经有值,则键值会被更新,否则就新生成该键。(因为返回的是当前Map对象,所以可以链式调用)

  • get(key) :该方法读取key对应的键值,如果找不到key,返回undefined。

  • has(key) :该方法返回一个布尔值,表示某个键是否在当前Map对象中。

  • delete(key) :该方法删除某个键,返回true,如果删除失败,返回false。

  • clear() :map.clear()清除所有成员,没有返回值。

Map结构原生提供是三个遍历器生成函数和一个遍历方法

  • keys():返回键名的遍历器。
  • values():返回键值的遍历器。
  • entries():返回所有成员的遍历器。
  • forEach():遍历Map的所有成员。
const map = new Map([
     ["foo",1],
     ["bar",2],
])
for(let key of map.keys()){
    console.log(key);  // foo bar
}
for(let value of map.values()){
     console.log(value); // 1 2
}
for(let items of map.entries()){
    console.log(items);  // ["foo",1]  ["bar",2]
}
map.forEach( (value,key,map) => {
     console.log(key,value); // foo 1    bar 2
})

(2)WeakMap

WeakMap 对象也是一组键值对的集合,其中的键是弱引用的。其键必须是对象,原始数据类型不能作为key值,而值可以是任意的。

该对象也有以下几种方法:

  • set(key,value) :设置键名key对应的键值value,然后返回整个Map结构,如果key已经有值,则键值会被更新,否则就新生成该键。(因为返回的是当前Map对象,所以可以链式调用)
  • get(key) :该方法读取key对应的键值,如果找不到key,返回undefined。
  • has(key) :该方法返回一个布尔值,表示某个键是否在当前Map对象中。
  • delete(key) :该方法删除某个键,返回true,如果删除失败,返回false。

其clear()方法已经被弃用,所以可以通过创建一个空的WeakMap并替换原对象来实现清除。

WeakMap的设计目的在于,有时想在某个对象上面存放一些数据,但是这会形成对于这个对象的引用。一旦不再需要这两个对象,就必须手动删除这个引用,否则垃圾回收机制就不会释放对象占用的内存。

而WeakMap的键名所引用的对象都是弱引用,即垃圾回收机制不将该引用考虑在内。因此,只要所引用的对象的其他引用都被清除,垃圾回收机制就会释放该对象所占用的内存。也就是说,一旦不再需要,WeakMap 里面的键名对象和所对应的键值对会自动消失,不用手动删除引用

总结:

  • Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。

  • WeakMap 结构与 Map 结构类似,也是用于生成键值对的集合。但是 WeakMap 只接受对象作为键名( null 除外),不接受其他类型的值作为键名。而且 WeakMap 的键名所指向的对象,不计入垃圾回收机制。