面试题 — JS篇
面试题 — JS篇
问:JS 原始数据类型有哪些?引用数据类型有哪些?
答:在JS中,存在7中原始数据类型,分别是:
boolean,null,undefined,number,string,symbol,bigint
引用类型:Object(包含普通对象 Object, 数组对象 Array, 正则对象 RegExp,日期对象 Date,数字函数 Math,函数对象 Function)
- Symbol表示独一无二的值,主要是用来定义对象的唯一属性名
- bigint 可以表示任意大小的整数
问:请说出下面代码的运行结果,并解释原因
function test(person) { |
答:因为在test函数传参的时候,实际上传递的是p1对象在堆中的内存地址值,所以通过调用person.age = 26确实改变了p1的值,但是随后person变成了另一块内存空间的地址,并且在最后将这个内存空间的地址返回,然后赋值给了p2
问:下面代码的输出结果是什么?(. 和 = 操作符的优先级)
let a = {n : 1} |
首先我们要知道 在js的运算中".“和”=“运算符同时出现时,会先执行”."运算
所以 a.x = a = {n:2} --> a.x = {n:2} --> a = {n:2}
问:0.1+0.2为什么不等于0.3?
答:(进制转换) js在做数字计算的时候,0.1和0.2都会被转成二进制后无限循环,但是js采用的IEEE 754二进制浮点运算,最大可以存储53位有效数字,于是大于53位后面的会全部截掉,将会导致精度缺失。
(对阶运算) 由于指数位数不相同,运算时需要对阶运算,这部分也可能产生精度缺失
https://juejin.cn/post/6940405970954616839
https://juejin.cn/post/6844903680362151950
你真的了解作用域吗?
var a = 0,b = 0; |
‘1’.toString()为什么可以调用?
实际上这个语句运行的过程中做了以下几件事:
var s = new Object("1") // 第一步:创建Object实例。 |
创建Object实例。注意为什么不是String?由于Symbol和BigInt的出现,对它们调用new都会报错,目前ES6规范也不建议用new来创建基本类型的包装类
整个过程体现了基本包装类型的性质,而基本包装类型恰恰属于基本数据类型,包括Boolean,Number和String
数据类型的判断
typeof:能判断所有值类型,函数。不可对null、对象、数组进行精确判断,因为都返回object
console.log(typeof undefined); // undefined
console.log(typeof 2); // number
console.log(typeof true); // boolean
console.log(typeof "str"); // string
console.log(typeof Symbol("foo")); // symbol
console.log(typeof 2172141653n); // bigint
console.log(typeof function () {}); // function
// 不能判别
console.log(typeof []); // object
console.log(typeof {}); // object
console.log(typeof null); // objectinstanceof:可用于判断对象类型
instanceof
运算符用于检测构造函数的prototype
属性是否出现在某个实例对象的原型链上。const Person = function() {}
const p1 = new Person() // p1.__proto__ === Person.prototype
p1 instanceof Person // true 即Person构造函数的prototype出现在实例p1的原型链上
var str1 = "hello"
str1 instanceof String // false(str1不是对象实例)
var str2 = new String("hello")
str2 instanceof String // trueObject.prototype.toString.call():所有原始数据类型都能判断
Object.prototype.toString.call(2); // "[object Number]"
Object.prototype.toString.call(""); // "[object String]"
Object.prototype.toString.call(true); // "[object Boolean]"
Object.prototype.toString.call(undefined); // "[object Undefined]"
Object.prototype.toString.call(null); // "[object Null]"
Object.prototype.toString.call(Math); // "[object Math]"
Object.prototype.toString.call({}); // "[object Object]"
Object.prototype.toString.call([]); // "[object Array]"
Object.prototype.toString.call(function () {}); // "[object Function]"
为什么要用call去调用toString方法呢?
借用Object原型对象上的toString方法,通过this绑定调用,返回
"[object 类型]"
如何判断变量是否为数组?
Array.isArray(arr); // true |
== 和 === 有什么区别?
=== 叫严格相等,左右两边不仅值要相等,类型也要相等
== 对于一般情况,只要值相等就返回 true,但 == 还涉及一些类型转换,转换规则如下:
- 两边类型是否相同,相同的话就比较值的大小,如:1==2 false
- 判断两边是否是 null 和 undefined,是的话就返回true
- 两边是否是 String 和 Number,是的话,就把String类型转换成 Number,再进行比较
- 判断其中一边是否是 Boolean,是的话就把Boolean转换成Number,再进行比较
- 如果其中一边为 Object,而另一边为String、Number或Symbol,会将Object转换成字符串,再进行比较
console.log({a: 1} == true) // false
console.log({a: 1} == "[object Object]") // true
[ ] == ![ ]的结果是什么?
[] == ![] // 逻辑运算需进行布尔值转换, [] -> true |
对象转原始类型是根据什么流程运行的?
对象转原始类型,会调用内置的 [ToPrimitive] 函数,对于该函数,其逻辑如下:
Symbol.toPrimitive
是一个内置的 Symbol 值,它是作为对象的函数值属性存在的,当一个对象转换为对应的原始值时,会调用此函数。
- 如果有Symbol.toPrimitive()方法,优先调用再返回
- 调用valueOf(),如果转换后为原始类型,则返回,否则继续
- 调用toString(),如果转换后为原始类型,则返回
- 如果都没有返回原始类型,会报错
var obj = { |
如何让 if(a == 1 && a == 2)条件成立?
如果 a 是对象,则在执行 == 比较的时候,会执行 valueOf 方法(或toString),因此我们可以重写该方法让条件成立
var a = { |
谈谈你对闭包的理解
什么是闭包?
MDN对JS闭包的解释:
- 一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure)。
- 也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。
- 在JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。
也可以说:
闭包是指那些能够访问自由变量的函数。自由变量是指在函数中使用的,但既不是函数参数也不是函数的局部变量的变量
闭包 = 函数 + 函数能够访问的自由变量
闭包的产生
在某个内部函数的执行上下文创建时,会将父级函数的活动对象(VO)加到内部函数的 [[scope]]中,形成作用域链,所以即使父级函数的执行上下文销毁,但是因为其活动对象实际上还存储在内存中可被内部函数访问到,从而实现了闭包
闭包的应用
函数作为参数被传递
var a = 1;
function foo(){
var a = 2;
function baz(){
console.log(a);
}
bar(baz);
}
function bar(fn){
// 这就是闭包
fn();
}
foo(); // 2函数作为返回值被返回
function foo() {
const a = 100
return function() {
console.log(a);
}
}
const fn = foo()
const a = 200
fn() // 100
闭包:自由变量的查找,是在函数定义的地方,向上级作用域查找。不是在执行的地方
如何解决下面的循环输出问题?
for(var i = 1; i <= 5; i ++){ |
为什么全部输出6?如何改进让它输出1,2,3,4,5?
因为setTimeout为宏任务,由于JS中单线程eventLoop机制,在主线程同步任务执行完后才去执行宏任务,因此循环结束后setTimeout中的回调才依次执行,输出i的时候,当前作用域没有,往上一层找,因为循环已结束,上层作用域中的i为6。因此会全部输出6
解决方法:
利用IIFE,每次for循环时,把此时的 i 变量传递到定时器中
IIFE( 立即调用函数表达式)是一个在定义时就会立即执行的 JavaScript 函数
for(var i = 1;i <= 5;i++){
(function(j){
setTimeout(function timer(){
console.log(j)
}, 0)
})(i)
}给定时器传入第三个参数,作为timer函数的第一个函数参数
定时器从第三个参数开始是附加参数,一旦定时器到期,它们会作为参数传递给function
for (var i = 1; i <= 5; i++) {
setTimeout(function(j) {
console.log(j);
}, 0, i)
}使用ES6中的let
let是块级作用域,用let后,变量只在它所在的代码块生效,没有什么作用域链了
// i = 1
{
setTimeout(function timer() {
console.log(1);
}, 0)
}
// i = 2
{
setTimeout(function timer() {
console.log(2);
}, 0)
}
// i = 3
{
setTimeout(function timer() {
console.log(3); // 3
}, 0)
}
...所以可以依次输出1,2,3,4,5
谈谈你对原型链的理解
- 原型对象和构造函数有何关系?
在JavaScript中,每当定义一个函数数据类型(普通函数、类)的时候,这个函数就会自带一个prototype属性,这个属性指向函数的原型对象
另外,这个原型对象会有一个constructor属性指回函数本身
当函数被new调用的时候,这个函数就称为了构造函数
new调用会创建一个实例对象,这个实例对象会自带一个__proto__
属性,这个属性指向构造函数的原型对象
即o.__proto__ === Foo.prototype
- 描述一下原型链?
JavaScript对象通过__proto__
指向父类原型对象,直到指向Object对象为止,这样就形成了一个原型指向的链条,即原型链
原型链最顶层的原型对象就是Object的原型对象
另外,检查对象自身是否包含某属性,可以使用 hasOwnProperty()
使用 in 的话,即使自身没有,但是原型链中有,也会返回true
绿色箭头,原型链
o.__proto__.__proto__.__proto__
带着掘金的各种文章一起看,找到自己想要的答案
做了一份前端面试复习计划,保熟~
链接:https://juejin.cn/post/7061588533214969892
神十三-原生JS灵魂之问, 请问你能接得住几个
链接:https://juejin.cn/post/6844903974378668039
字节跳动最爱考的前端面试题:JavaScript 基础
链接:https://juejin.cn/post/6934500357091360781
「查缺补漏」送你 54 道 JavaScript 面试题
链接:https://juejin.cn/post/6854573211443544078#comment