面试题第四期

129 阅读20分钟

9月29日

1.JavaScript有哪些数据类型,它们的区别?

JavaScript共有八种数据类型,分别是 Undefined、Null、Boolean、Number、String、Object、Symbol、BigInt\

  1. Undefined 表示未定义
  2. Null 表示空
  3. Boolean 布尔类型 true和false
  4. Number 数字类型 取值范围是(+2^53-1至-2^53-1)
  5. String 表示文本数据
  6. Object Math Function Deta RegExp(正则)
  7. Symbol
  8. BigInt

其中 Symbol 和 BigInt 是ES6 中新增的数据类型:

  • Symbol创建后是一个独一无二且不可变的数据类型
  • BigInt 是一个基础数据类型 可以表示任何精度的整数 并且可以超过数字类型的取值范围

这些数据类型可以分为基本数据类型和引用数据类型

  • 堆:基本数据类型 Undefined Null Boolean Number String
  • 栈:引用数据类型 Object Function Array

堆和栈的概念存在于数据结构和操作系统内存中,在数据结构中:

  • 在数据结构中,栈中数据的存取方式为先进后出。 类似于羽毛球桶
  • 堆是一个优先队列,是按优先级来进行排序的,优先级可以按照大小来规定。 类似于排队 先到先走

两种类型的区别在于存储位置的不同

  • 基本数据类型存在堆里面 值与值之间是独立存在的,修改一个变量不会改变影响其它的变量;
  • 引用数据类型存在栈里面 每创建一个新的对象,就会在堆内存中开辟出一个新的空间。而变量保存的是对象的内存地址(对象的引用),如果两个变量保存的是同一个对象引用,每当通过修改随意一个变量修改属性时,另一个也会受到影响

2. 数据类型检测的方式有哪些

  1. typeof
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可以判断对象的类型 而不能判断基本数据类型。原理是instanceof是从原型链上找的instanceof 运算符可以用来测试一个对象在其原型链中是否存在一个构造函数的 prototype 属性。

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

4.Object.prototype.toString.call() Object.prototype.toString.call() 使用 Object 对象的原型方法 toString 来判断数据类型:

3. 判断数组的方式有哪些

  • 通过Object.prototype.toString.call()做判断
Object.prototype.toString.call(obj).slice(8,-1) === 'Array';
  • 通过原型链做判断
obj.__proto__ === Array.prototype;
  • 通过ES6的Array.isArray()做判断
Array.isArrray(obj);
  • 通过instanceof做判断
obj instanceof Array
  • 通过Array.prototype.isPrototypeOf
Array.prototype.isPrototypeOf(obj)

4、null和undefined区别

null和undefined都是基础数据类型 这两个基本数据类型分别都只有一个值,就是 undefined 和 null。
undefined表示未定义 null表示空对象 一般变量声明了但还没有定义的时候会返回 undefined,null主要用于赋值给一些可能会返回对象的变量,作为初始值。
undefined在js中不是一个保留字 所以可以用来作为变量名 但是这样很危险 它会影响对 undefined 值的判断

5. typeof null 的结果是什么,为什么?

typeof null 的结果是object
因为js初始版本时 值是由32位存储 前三位表示的是数据类型 而000便是object类型 null 的值是机器码 NULL 指针(null 指针的值全是 0) 所以就和object类型相同

6.intanceof 操作符的实现原理及实现

instanceof 运算符用于判断构造函数的 prototype 属性是否出现在对象的原型链中的任何位置。

function myInstanceof(left, right) {
  // 获取对象的原型
  let proto = Object.getPrototypeOf(left)
  // 获取构造函数的 prototype 对象
  let prototype = right.prototype; 
 
  // 判断构造函数的 prototype 对象是否在对象的原型链上
  while (true) {
    if (!proto) return false;
    if (proto === prototype) return true;
    // 如果没有找到,就继续从其原型上找,Object.getPrototypeOf方法用来获取指定对象的原型
    proto = Object.getPrototypeOf(proto);
  }
}

7.为什么0.1+0.2 ! == 0.3,如何让其相等

因为在 0.1+0.2 的计算过程中发生了精度丢失。在JS内部所有的计算都是以二进制方式计算的, 在 0.1 和 0.2 转成双精度二进制浮点数时,由于二进制浮点数的小数位只能存储52位,导致小数点后第53位的数要进行舍弃操作,从而造成精度丢失。最终导致 0.1+0.2 不等于0.3 。

如何相等呢?

可以将其转换为整数后再进行运算,运算后再转换为对应的小数

var result = (a * 100 + b * 100) / 100

8 如何获取安全的 undefined 值?

因为 undefined 是一个标识符,所以可以被当作变量来使用和赋值,但是这样会影响 undefined 的正常判断。表达式 void ___ 没有返回值,因此返回结果是 undefined。void 并不改变表达式的结果,只是让表达式不返回值。因此可以用 void 0 来获得 undefined。

9. typeof NaN 的结果是什么?

NaN 指“不是一个数字”(not a number),NaN 是一个“警戒值”(sentinel value,有特殊用途的常规值),用于指出数字类型中的错误情况,即“执行数学运算没有成功,这是失败后返回的结果

10 其他值到字符串的转换规则?

  • Null 和 Undefined 类型 ,null 转换为 "null",undefined 转换为 "undefined",
  • Boolean 类型,true 转换为 "true",false 转换为 "false"。
  • Number 类型的值直接转换,不过那些极小和极大的数字会使用指数形式。
  • Symbol 类型的值直接转换,但是只允许显式强制类型转换,使用隐式强制类型转换会产生错误。
  • 对普通对象来说,除非自行定义 toString() 方法,否则会调用 toString()(Object.prototype.toString())来返回内部属性 [[Class]] 的值,如"[object Object]"。如果对象有自己的 toString() 方法,字符串化时就会调用该方法并使用其返回值

11 其他值到布尔类型的值的转换规则?

以下这些是假值: • undefined • null • false • +0、-0 和 NaN • ""

假值的布尔强制类型转换结果为 false。从逻辑上说,假值列表以外的都应该是真值。

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

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

9月30号

13. || 和 && 操作符的返回值?

||操作符表示或运算,如果不是布尔值,则就会进行ToBoolean操作,然后再进行条 件判断。
对于 || 来说,如果条件结果为true就返回第一个操作数的值,如果为false就返回 第二个操作数的值。(注意,返回的是值)
&&表示与运算,如果条件判断结果是true,则就返回第二个操作数的值,如果为 false就返回第一个操作数的值。
|| 和&& 返回的是其中一个操作数,并非条件判断。

14、Object.is() 与比较操作符 “===”、“==” 的区别?

  • 使用双等号(==)进行相等判断时,如果两边的类型不一致,则会进行强制类型转化后再进行比较。
  • 使用三等号(===)进行相等判断时,如果两边的类型不一致时,不会做强制类型准换,直接返回 false。
  • 使用 Object.is 来进行相等判断时,一般情况下和三等号的判断相同,它处理了一些特殊的情况,比如 -0 和 +0 不再相等,两个 NaN 是相等的。

15.+ 操作符什么时候用于字符串的拼接

如果 + 的其中一个操作数是字符串,则执行字符串拼接,否则执行数字加法。

16  如果new一个箭头函数的会怎么样

箭头函数是ES6中的提出来的,它没有prototype,也没有自己的this指向,更不可以使用arguments参数,所以不能New一个箭头函数。

17 new操作符的实现步骤

  1. new构造函数后,会在内存中创建一个空对象
  2. this就会指向刚才创建的这个空对象
  3. 执行构造函数中代码,给空对象添加属性方法
  4. 返回这个新的对象(所以构造函数中不需要return)

18.箭头函数与普通函数的区别

  1. 外形不同:箭头函数使用箭头定义,普通函数中没有。
  2. 箭头函数全都是匿名函数:普通函数可以有匿名函数,也可以有具名函数
  3. 箭头函数不能用于构造函数:普通函数可以用于构造函数,以此创建对象实例。
  4. 箭头函数中 this 的指向不同:在普通函数中,this 总是指向调用它的对象,如果用作构造函数,它指向创建的对象实例。
  5. 箭头函数不具有 arguments 对象:每一个普通函数调用后都具有一个 arguments 对象,用来存储实际传递的参数。但是箭头函数并没有此对象。
  6. 其他区别:箭头函数不具有 prototype 原型对象。箭头函数不具有 super。箭头函数不具有 new.target

19. 箭头函数的this指向哪⾥?

箭头函数不同于传统JavaScript中的函数,箭头函数并没有属于⾃⼰的this,它所谓的this是捕获其所在上下⽂的 this 值,作为⾃⼰的 this 值,并且由于没有属于⾃⼰的this,所以是不会被new调⽤的,这个所谓的this也不会被改变。

可以⽤Babel理解⼀下箭头函数:

// ES6 
const obj = { 
  getArrow() { 
    return () => { 
      console.log(this === obj); 
    }; 
  } 
}
复制代码

转化后:

// ES5,由 Babel 转译
var obj = { 
   getArrow: function getArrow() { 
     var _this = this; 
     return function () { 
        console.log(_this === obj); 
     }; 
   } 
};

20扩展运算符的作用及使用场景

1、作为函数的形参

在作为函数的形参时,通过 …数组名 来表示,也称为rest参数,当函数被调用时传入的实参全部会被放入到这个数组中。

let fn = function(...list){
	console.log(list)
}

fn(1,2,3) //[1,2,3]·

2.结合数组使用

当扩展运算符结合数组使用时,可以将一个数组转为用逗号分隔的参数序列,且每次只能展开一层数组。

(1)用于复制数组:

const arr1 = [1, 2];
const arr2 = [...arr1]; //arr2 = [1,2]

(2)用于合并数组:

const arr1 = [2, 3];
const arr2 = [1, ...arr1, 4, 5]; //arr2 = [1,2,3,4,5]

(3)作为函数的实参:

将数组的元素展开以逗号分隔传入函数

function add(x, y) {
  return x + y;
}
const numbers = [1, 2];
add(...numbers) // 3

(4)扩展运算符与解构赋值结合起来,用于生成数组:

const [a, ...list] = [1, 2, 3, 4, 5]; // a = 1  // list = [2, 3, 4, 5]

这种情况需要注意的是:扩展运算符只能放在参数的最后一位,不然会报错

const [...list, a] = [1, 2, 3, 4, 5]; //报错

(5)将字符串或则特殊的对象转换成数组(带有Iterator 接口的对象,如类数组对象):

//将字符串转换成数组
let stringList = [...'hello']    //stringList = [ "h", "e", "l", "l", "o" ]

//将带有Iterator接口的对象转换成数组
// 这里用arguments对象举例
function fn() {
  let list = [...arguments];
  console.log(list)
}

fn(1,2,3) //[1,2,3]

3、结合对象使用

当扩展运算符结合对象使用时,用于取出参数对象中的所有可遍历属性,拷贝到当前对象之中。

let obj = { a: 1, b: 2 };
let newObj = { ...obj }; // { a: 1, b: 2 }

需要注意的是:

  • 扩展运算符对对象实例的拷贝属于浅拷贝
  • 如果拷贝过程中有同名属性会进行覆盖:
let obj = {a: 1, b: 2};
let newObj = {...obj, ...{a:3, b: 4}};  // {a: 3, b: 4}

21 对对象与数组的解构的理解

解构是 ES6 提供的一种新的提取数据的模式,这种模式能够从对象或数组里有针对性地拿到想要的数值。

1)数组的解构

在解构数组时,以元素的位置为匹配条件来提取想要的数据的:

const [a, b, c] = [1, 2, 3] //a=1 b=2 c=3

最终,a、b、c分别被赋予了数组第0、1、2个索引位的值:

数组里的0、1、2索引位的元素值,精准地被映射到了左侧的第0、1、2个变量里去,这就是数组解构的工作模式。还可以通过给左侧变量数组设置空占位的方式,实现对数组中某几个元素的精准提取:

const [a,,c] = [1,2,3] //a=1 c=3

2)对象的解构

对象解构比数组结构稍微复杂一些,也更显强大。在解构对象时,是以属性的名称为匹配条件,来提取想要的数据的。现在定义一个对象:

const stu = {
  name: 'Bob',
  age: 24
}

假如想要解构它的两个自有属性,可以这样:

const { name, age } = stu //name = 'Bob' age=24

这样就得到了 name 和 age 两个和 stu 平级的变量:

注意,对象解构严格以属性名作为定位依据,所以就算调换了 name 和 age 的位置,结果也是一样的:

const { age, name } = stu //name = 'Bob' age=24

22. 如何提取高度嵌套的对象里的指定属性?

有时会遇到一些嵌套程度非常深的对象:

const school = {
   classes: {
      stu: {
         name: 'Bob',
         age: 24,
      }
   }
}

像此处的 name 这个变量,嵌套了四层,此时如果仍然尝试老方法来提取它:

const { name } = school

显然是不奏效的,因为 school 这个对象本身是没有 name 这个属性的,name 位于 school 对象的“儿子的儿子”对象里面。要想把 name 提取出来,一种比较笨的方法是逐层解构:

const { classes } = school
const { stu } = classes
const { name } = stu
name // 'Bob'

但是还有一种更标准的做法,可以用一行代码来解决这个问题:

const { classes: { stu: { name } }} = school
       
console.log(name)  // 'Bob'

可以在解构出来的变量名右侧,通过冒号+{目标属性名}这种形式,进一步解构它,一直解构到拿到目标数据为止。

23. 对 rest 参数的理解

扩展运算符被用在函数形参上时,它还可以把一个分离的参数序列整合成一个数组

function mutiple(...args) {
  let result = 1;
  for (var val of args) {
    result *= val;
  }
  return result;
}
mutiple(1, 2, 3, 4) // 24

这里,传入 mutiple 的是四个分离的参数,但是如果在 mutiple 函数里尝试输出 args 的值,会发现它是一个数组:

function mutiple(...args) {
  console.log(args)
}
mutiple(1, 2, 3, 4) // [1, 2, 3, 4]

这就是 … rest运算符的又一层威力了,它可以把函数的多个入参收敛进一个数组里。这一点经常用于获取函数的多余参数,或者像上面这样处理函数参数个数不确定的情况。

24. ES6中模板语法与字符串处理

ES6 提出了“模板语法”的概念。在 ES6 以前,拼接字符串是很麻烦的事情:

var name = 'css'   
var career = 'coder' 
var hobby = ['coding', 'writing']
var finalString = 'my name is ' + name + ', I work as a ' + career + ', I love ' + hobby[0] + ' and ' + hobby[1]

仅仅几个变量,写了这么多加号,还要时刻小心里面的空格和标点符号有没有跟错地方。但是有了模板字符串,拼接难度直线下降:

var name = 'css'   
var career = 'coder' 
var hobby = ['coding', 'writing']
var finalString = `my name is ${name}, I work as a ${career} I love ${hobby[0]} and ${hobby[1]}`

字符串不仅更容易拼了,也更易读了,代码整体的质量都变高了。这就是模板字符串的第一个优势——允许用${}的方式嵌入变量。但这还不是问题的关键,模板字符串的关键优势有两个:

  • 在模板字符串中,空格、缩进、换行都会被保留
  • 模板字符串完全支持“运算”式的表达式,可以在${}里完成一些计算

基于第一点,可以在模板字符串里无障碍地直接写 html 代码:

let list = `
	<ul>
		<li>列表项1</li>
		<li>列表项2</li>
	</ul>
`;
console.log(message); // 正确输出,不存在报错

基于第二点,可以把一些简单的计算和调用丢进 ${} 来做:

function add(a, b) {
  const finalString = `${a} + ${b} = ${a+b}`
  console.log(finalString)
}
add(1, 2) // 输出 '1 + 2 = 3'

除了模板语法外, ES6中还新增了一系列的字符串方法用于提升开发效率:

(1)存在性判定:在过去,当判断一个字符/字符串是否在某字符串中时,只能用 indexOf > -1 来做。现在 ES6 提供了三个方法:includes、startsWith、endsWith,它们都会返回一个布尔值来告诉你是否存在。

  • includes:判断字符串与子串的包含关系:
const son = 'haha' 
const father = 'xixi haha hehe'
father.includes(son) // true
  • startsWith:判断字符串是否以某个/某串字符开头:
const father = 'xixi haha hehe'
father.startsWith('haha') // false
father.startsWith('xixi') // true
  • endsWith:判断字符串是否以某个/某串字符结尾:
const father = 'xixi haha hehe'
  father.endsWith('hehe') // true

(2)自动重复:可以使用 repeat 方法来使同一个字符串输出多次(被连续复制多次):

const sourceCode = 'repeat for 3 times;'
const repeated = sourceCode.repeat(3) 
console.log(repeated) // repeat for 3 times;repeat for 3 times;repeat for 3 times;

25.CSS 选择器及优先级

选择器

  • id选择器(#myid)
  • 类选择器(.myclass)
  • 属性选择器(a[rel="external"])
  • 伪类选择器(a:hover, li:nth-child)
  • 标签选择器(div, h1,p)
  • 相邻选择器(h1 + p)
  • 子选择器(ul > li)
  • 后代选择器(li a)
  • 通配符选择器(*)

优先级:

  • !important
  • 内联样式(1000)
  • ID选择器(0100)
  • 类选择器/属性选择器/伪类选择器(0010)
  • 元素选择器/伪元素选择器(0001)
  • 关系选择器/通配符选择器(0000)

带!important 标记的样式属性优先级最高; 样式表的来源相同时:!important > 行内样式>ID选择器 > 类选择器 > 标签 > 通配符 > 继承 > 浏览器默认属性

26 margin 和 padding 的使用场景

  • 需要在border外侧添加空白,且空白处不需要背景(色)时,使用 margin;
  • 需要在border内侧添加空白,且空白处需要背景(色)时,使用 padding。

27 arguments的作用

  • 是一个类数组对象,可以通过索引类操作实参,也可以获取实参长度
  • arguments经常作为隐式参数 作用把函数的实参不经过函数形参 直接函数内部就能接收到 在源码中很常见

27 arguments 解释和实际使用场景

在定义函数的时候,浏览器会给函数传入两个隐匿的参数
函数的上下文对象 this 和 封装实参的对象 arguments

10月1号

28 new操作符的实现原理

构造函数的内部原理,简单的分为以下的四个步骤:
1.创建一个新对象;
2.链接到原型(将构造函数的 prototype 赋值给新对象的 proto);
3.绑定this(构造函数中的this指向新对象并且调用构造函数);
4.返回新对象;

29 Object和Map的区别?

相同点

二者都是以key-value的形式对数据进行存储;

不同点
1.key的数据类型范围不同

Object:可以作为key的有:number,string,以及es6里面的symbol; 
Map:js目前存在的数据类型均可以作为key;

2.key的顺序

Object: 如果对象的key中同时存在number string symbol 三种类型的时候,通过Object.keys得到的顺序是数字(升序) -> string(symbol)以创建的顺序;
Map: key以声明的顺序进行排序;

3.创建方式不同

Object:创建方式 
const obj1 = new Object()
const obj2 = {} 
const obj3 = Object.create({}) 
Map:创建方式: 
const map = new Map();

4.key的调用方式不同

通过key取值: Object:可通过 . 或 [] 
Map:只能用原生的get方法进行调用; 
判断是否有某个属性 
Object:'a' in obj;判断obj中是否有a这个属性; 
Map:map.has('a');判断map中是否有a这个属性;

5.设置属性的方式不同

Object: 
1.obj.a = 1; 
2.obj['a'] = 1; 
Map:js目前存在的数据类型均可以作为key; 
1.map.set('a',1)

6.删除key的方式

Object: 自身没有删除属性的方法;
一般删除对象属性的方式: 
delete obj.a 
Map: 
map.delete('a') ----删除a属性; 
map.clear() ----删除所有的属性;

7.获取size

Object: 通过Object.keys(obj) 返回一个数组,通过获取数组的长度来获取size; 
Map: 自身带有size属性;map.size,size属性无法修改;

8.Iterating(迭代)

Object: 不可以
Map: 可以;

如何判断一个数据是否可以迭代的方式
typeof [][Symbol.iterator] //function
typeof new Map()[Symbol.iterator] //function
typeof {}[Symbol.iterator]  //undefined
typeof 1[Symbol.iterator] //undefined

9.JSON操作

Object: 支持JSON.stringifyJSON.parse的操作;
Map: 不支持;

10.this不同

const f = function(){ console.log(this) }
Object: 
const obj = {fn:f}
Map: 
const map = new Map()
map.set('fn',f)
		
obj.fn() //指向obj
map.get('fn')() //取决于函数的调用者;

30 javascript中常见的内置对象有哪些

全局的对象不要和 "全局对象(global object)" 混淆。这里说的全局的对象是说在 全局作用域里的对象。全局作用域中的其他对象可以由用户的脚本创建或由宿主程序提供。

  1. Array对象:提供一个数组的模型来存储大量有序的数据。
  2. Math对象:可以处理所有的数学运算 。
  3. Date对象:可以处理日期和时间的存储、转化和表达。
  4. String对象:可以处理所有的字符串的操作。

31. 常用的正则表达式有哪些?

//(1)匹配 16 进制颜色值 
var regex = /#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})/g; 
//(2)匹配日期,如 yyyy-mm-dd 格式
var regex = /^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$/;  
// (3)匹配 qq 号
var regex = /^[1-9][0-9]{4,10}$/g;
// (4)手机号码正则
var regex = /^1[34578]\d{9}$/g; 
//(5)用户名正则 
var regex = /^[a-zA-Z\$][a-zA-Z0-9_\$]{4,16}$/;

32.对JSON的理解

1.json是一种轻量级的数据交换格式,在与后端的数据交互中具有较为广泛的应用
2.在javaScript中,我们可以直接使用json,因为JavaScript中内置了json的解析,把任何的JavaScript对象变成json,就是把这个对象序列化成一个json格式的字符串,这样才能通过网络传递给其他计算机。如果我们收到json格式的字符串,只需要把它反序列化为一个JavaScript对象,就可以在JavaScript中直接使用这个对象了
3.JSON 是适用于 Ajax 应用程序的一种有效格式,原因是它使 JavaScript 对象和字符串值之间得以快速转换 JSON是一种传递对象的语法。JSON是一个提供了stringify和parse方法的内置对象。
stringify将js对象转化为符合json标准的字符串。
parse将符合json标准的字符串转化为js对象。

33. display的block、inline和inline-block的区别

(1)block: 会独占一行,多个元素会另起一行,可以设置width、height、margin和padding属性;

(2)inline: 元素不会独占一行,设置width、height属性无效。但可以设置水平方向的margin和padding属性,不能设置垂直方向的padding和margin;

(3)inline-block: 将对象设置为inline对象,但对象的内容作为block对象呈现,之后的内联对象会被排列在同一行内。

对于行内元素和块级元素,其特点如下:

(1)行内元素

  • 设置宽高无效;
  • 可以设置水平方向的margin和padding属性,不能设置垂直方向的padding和margin;
  • 不会自动换行;

(2)块级元素

  • 可以设置宽高;
  • 设置margin和padding都有效;
  • 可以自动换行;
  • 多个块状,默认排列从上到下。