缘起

笔者最近在近两周的面试中, 遇到了大大小小形形色色的面试题。很多问题都是知道但是说不出来,所以想记录下来, 希望可以帮到大家,也可以方便自己以后查阅。
面试题无疑就是那几种类型的题, 所以笔者将按分类 分几次发, 也会有对应的问题扩展的解答。 希望可以帮到大家。
说明一下,面试题远不止这些,笔者只是就自己所遇到的做一个记录。若有说的不对的地方,还请大家指出来,大家共同进步!

Js基础的数据类型相关问题

1. Js 的基础数据类型有哪些?

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

数据类型扩展问题

1-1. js 的引用数据类型有哪些?

引用数据类型:

ObjectObjectArrayFunctionRegExpDateMath

1-2. 0.1+0.2 为什么不等于 0.3?

0.10.20.30000000000000004

1-3. 为什么添加BigInt数据类型?

JSNumber2^53 - 12^53 - 1BigIntBigInt
console.log(999999999999999);  //=>10000000000000000

// 由于精度受损,也会有一定的安全性问题,如下
9007199254740992 === 9007199254740993;    // -> true 居然是true!

BigInt

1-4. null 是对象吗?

typeof null // -> "object"
答案:
typeofnullobjectnull000nullobject
关于js 判断数据类型的方法

js 判断数据类型的方法有哪几种?

方法1. 使用 typeof 判断数据类型

nulltypeof
function"object"
typeof

方法2. 使用instanceof 判断数据类型

instanceoftrue

注意:

1 instanceof Number // false
'1' instanceof String  // false

let a= {};
a instanceof Object  // true
instanceof

方法3. 使用 Object.prototype.toString.call 方法判断

实际是继承Object 的原型方法判断

Object.prototype.toString.call('1')  //  "[object String]"
Object.prototype.toString.call(1)  //  "[object Number]"
Object.prototype.toString.call({})  //  "[object Object]"

使用这个方法就可以对基本数据类型和引用数据类型的做一个区分了。

关于 == 和 === 的区别?

关于 == 的一些说明

==
'1'==1  //  true
==trueNaNNaN==
  • 两边数据类型相同,就比较值的大小是否相等;
  • 数据类型不同时:
nullundefinedtrue
null == undefined  //  true
null == null  //  true
undefined == undefined  //  true
stringnumberstringnumber
'1' == 1  // true

.

BooleanBooleanNumber10ObjectStringNumberSymbolObject
console.log({a: 1} == true);//false
console.log({a: 1} == "[object Object]");//true

.

关于 === 全等于的一些说明

===falsetrue

所以避免出现一些错误的判断都建议使用 === 来对数据进行一个相等判断;

NaNNaNfalse
NaNisNaN()
isNaN(NaN)  //  true
JS 中类型转换有哪些?

主要分为三类:

  • 转为数字
  • 转为字符串
  • 装维布尔值
    具体转换规则如下:


    js数据类型转换

    图表形式:


    在这里插入图片描述
关于闭包

什么是闭包?闭包有哪些作用?

(红宝书)闭包是指那些引用了另外一个函数作用域中变量的函数,通常是在嵌套函数中实现的。

闭包的作用:

  • 读取函数内部的变量
  • 让这些变量的值保存在内存里面,实现数据共享
null

闭包有那些表现形式?

1.函数作为返回值

var num = 2
function outer() {
 var num = 0 //内部变量
 return function add() {
 //通过return返回add函数,就可以在outer函数外访问了。
 num++ //内部函数有引用,作为add函数的一部分了
 console.log(num)
 }
}

var func1 = outer() //
func1() //实际上是调用add函数, 输出1

2.函数作为参数传递

var num = 2
function outer() {
 var num = 0 //内部变量
 function add() {
    num++ //内部函数有引用,作为add函数的一部分了
    console.log(num)
 }
 foo (add)
}
function foo (fn){
  // 函数以参数的方式传递执行 闭包 
    fn()
}

outer() // 1

3.在定时器、事件监听、Ajax请求、跨窗口通信、Web Workers或者任何异步中,只要使用了回调函数,实际上就是在使用闭包。

局作用域 window当前作用域
setTimeOut(function timeHandler(){
    console.log(123)
},100)
全局作用域window当前函数作用域
var num = 5;
(function init(){
    console.log(num)  // 5
})()

闭包的经典笔试题

1.以下代码输出什么 ? 为什么 ? 如何修改 ?

 for ( var i=1;i<=5;i++) {
    setTimeout(function timer(){
          console.log(i);
     },i*1000)
}
setTimeoutsetTimeoutii66

解决办法
1. 使用 闭包(立即执行函数的形式)

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

2. 使用 setTimeout的第三个参数

for ( var i=1;i<=5;i++) {
    setTimeout(function timer(j){
          console.log(j);
     },i*1000,i)
} // 1,2,3,4,5
let
 for ( let i=1;i<=5;i++) {
    setTimeout(function timer(){
          console.log(i);
     },i*1000)
}
letlet
对原型链的理解
__proto__Object

原型链的顶端是什么?

Object.prototype

Object 的原型最后是什么?

objectnull
console.log(Object.prototype.__proto__ === null),返回true

null 表示没有对象,即该处不应有值,所以Object.prototype没有原型。

proto是什么

function Person(){
}
访问原型Person.prototype
Object.prototypeobj.__proto__Object.getPrototype(obj)
js有几种继承方法
复制操作

以下继承方法参考js灵魂之问

第一种: 借助call

 function Parent1(){
    this.name = 'parent1';
  }
  Parent1.prototype.add = function () {}
  
  function Child1(){
    Parent1.call(this);
    this.type = 'child1'
  }
  console.log(new Child1);

注意: 这种方式子类只能拿到父类的属性,获取不到父类原型对象中的方法。

第二种: 原型链继承

  function Parent2() {
    this.name = 'parent2';
    this.play = [1, 2, 3]
  }
 Parent2.prototype.add = function () {}
 
  function Child2() {
    this.type = 'child2';
  }
  Child2.prototype = new Parent2();

  console.log(new Child2());
结果如下:

这种方法是可以成功继承到父类的属性和原型方法,但是有个潜在的问题,如下:

  var s1 = new Child2();
  var s2 = new Child2();
  s1.play.push(4);
  console.log(s1.play, s2.play);

注意: 修改
s1
的属性值,
s2
的属性也变化了,因为两个实例使用的是同一个原型对象。

call 和原型 的组合继承

  function Parent3 () {
    this.name = 'parent3';
    this.play = [1, 2, 3];
  }
   Parent3.prototype.add = function () {}

  function Child3() {
    Parent3.call(this); // 执行一次
    this.type = 'child3';
  }
  Child3.prototype = new Parent3();  // 执行两次
  console.log(new Child3 () )
  
  var s3 = new Child3();
  var s4 = new Child3();
  s3.play.push(4);
  console.log(s3.play, s4.play);


可以看到属性和方法都继承了,并且也没有共享实例的问题。但是这一种写法其实也有一个问题,那就是
Parent3
的构造函数会执行两次,增加了性能的消耗。

组合继承 优化

  function Parent4 () {
    this.name = 'parent4';
    this.play = [1, 2, 3];
  }
   Parent4.prototype.add = function () {}
    
  function Child4() {
    Parent4.call(this);
    this.type = 'child4';
  }
  Child4.prototype = Parent4.prototype;
  console.log(new Child4())

将父类原型对象直接给到子类,父类构造函数只执行一次,而且父类的属性和方法都能继承.


注意: 子类实例的构造函数 是
Parent4
,显然这是不对的,应该是
Child4

(推荐)寄生组合继承

  function Parent5 () {
    this.name = 'parent5';
    this.play = [1, 2, 3];
  }
   Parent5.prototype.add = function () {}
   
  function Child5() {
    Parent5.call(this);
    this.type = 'child5';
  }
  Child5.prototype = Object.create(Parent5.prototype);
  Child5.prototype.constructor = Child5;

这是最推荐的一种方式,接近完美的继承,也是组合继承的进阶版。

ES6 的extends 继承

extendsObject.create

extends 的语法

class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
}

class ColorPoint extends Point {
  constructor(x, y, color) {
    super(x, y);
    this.color = color; // 正确
  }
}
super

面向对象的设计不一定是最好的,还是的依据具体的业务场景判断。

继承的最大问题在于:无法决定继承哪些属性,所有属性都得继承。

一方面父类是无法描述所有子类的细节情况的,为了不同的子类特性去增加不同的父类,代码势必会大量重复,另一方面一旦子类有所变动,父类也要进行相应的更新,代码的耦合性太高,维护性不好。

那如何来解决继承的诸多问题呢?

面向组合的设计方式

.

参考:
原生JS灵魂之问
yck前端面试之道
原型链的详细讲解