打卡学习-JavaScript面试题(二)

195 阅读8分钟

菜鸡打卡

1.复杂数据类型如何转变为字符串

  • 首先,会调用 valueOf 方法,如果方法的返回值是一个基本数据类型,就返回这个值,如果调用 valueOf 方法之后的放回置仍旧是一个复杂数据类型,就会调用该对象的 toString 方法,如果 toString 方法调用之后的返回值是一个基本数据类型,就会返回这个值,如果 toString 方法调用之后的返回值是一个复杂数据类型,就报一个错误。
//1
const obj = {
    valueOf: function () {
        return 1
    }
}
console.log(obj + '') // '1'

//2
const obj2 = {
    valueOf: function () {
        return [1, 2]
    }
}
console.log(obj2 + '')//[object Object]

//3
const obj3 = {
    valueOf: function () {
        return [1, 2]
    },
    toString: function () {
        return 1
    }
}
console.log(obj3 + '') //'1'

//4
const obj4 = {
    valueOf: function () {
        return [1, 2]
    },
    toString: function () {
        return [1, 2, 3]
    }
}
console.log(obj4 + '')// 报错 Uncaught TypeError: Cannot convert object to primitive value

扩展

let arr = [new Object(), new Date(), new RegExp(), new String(), new Number(), new Boolean(),
    new Function(), new Array(), Math]
console.log(arr.length); // 9

for (let i=0; i<arr.length; i++) {
    arr[i].valueOf = function () {
        return [1, 2, 3]
    }
    arr[i].toString = function () {
        return 'toString'
    }
    console.log(arr[i] + '')
}

1.若 return[1,2,3]处为 return "valueof",得到的返回值是valueof toString 7valueof 说明:其他八种复杂类型是调用valueOf方法,时间对象调用toString方法。

2.改成return[1,2,3],得到的返回值是 9toString 说明:执行 valueof 后都来执行 toString。

2.javascript 的 typeof 返回哪些数据类型

7 种分别为 string、boolean、number、Object、Function、undefined、symbol(ES6),bigInt(不考虑这个,没用过哈哈)

//number
typeof (10);
typeof (NaN); // NaN在JavaScript中代表的是特殊非数字值,它本身是一个数字类型。
typeof (Infinity)

//boolean
typeof(true);
typeof(false);

//string
typeof("abc");

//undefined
typeof(undefined);
typeof(a); // 不存在的变量

//object
// 对象,数组,null返回object
typeof(null);
typeof(window);

//function
typeof(Array);
typeof(Date);

//symbol
typeof Symbol() // ES6提供的新的类型

3.一次 js 请求一般情况下有哪些地方会有缓存处理?

  • DNS 缓存
  • CDN 缓存
  • 浏览器缓存
  • 服务器缓存

DNS 缓存

DNS 缓存指 DNS 返回了正确的 IP 之后,系统就会将这个结果临时储存起来。并且它会为缓存设定一个失效时间 (例如 N 小时),在这 N 小时之内,当你再次访问这个网站时,系统就会直接从你电脑本地的 DNS 缓存中把结果交还给你,而不必再去询问 DNS 服务器,变相“加速”了网址的解析。当然,在超过 N 小时之后,系统会自动再次去询问DNS 服务器获得新的结果。 所以,当你修改了 DNS 服务器,并且不希望电脑继续使用之前的 DNS 缓存时,就需要手动去清除本地的缓存了。

本地 DNS 迟迟不生效或者本地 dns 异常等问题,都会导致访问某些网站出现无法访问的情况,这个时候我们就 需要手动清除本地 dns 缓存,而不是等待!

CDN 缓存

和 Http 类似,客户端请求数据时,先从本地缓存查找,如果被请求数据没有过期,拿过来用,如果过期,就向 CDN 边缘节点发起请求。CDN 便会检测被请求的数据是否过期,如果没有过期,就返回数据给客户端,如果过期,CDN 再向源站发送请求获取新数据。和买家买货,卖家没货,卖家再进货一个道理^-^。

CDN 边缘节点缓存机制,一般都遵守 http 标准协议,通过 http 响应头中的 Cache-Control 和 max-age 的字 段来设置

CDN 边缘节点的数据缓存时间。

浏览器缓存

浏览器缓存(Browser Caching)是为了节约网络的资源加速浏览,浏览器在用户磁盘上对 近请求过的文档进行存储,当访问者再次请求这个页面时,浏览器就可以从本地磁盘显示文档,这样就可以加速页面的阅览。

浏览器缓存主要有两类:缓存协商:Last-modified ,Etag 和彻底缓存:cache-control,Expires。浏览器都有对应清除缓存的方法。

服务器缓存

服务器缓存有助于优化性能和节省宽带,它将需要频繁访问的 Web 页面和对象保存在离用户更近的系统中,当再次访问这些对象的时候加快了速度。

4.你对闭包的理解?优缺点?

概念:闭包就是能够读取其他函数内部变量的函数。

三大特新:

  • 函数嵌套函数
  • 函数内部可以引用外部的参数和变量
  • 参数和变量不会被垃圾回收机制回收

优点

  • 希望一个变量长期储存在内存中
  • 避免内部可以引用外部的参数和变量
  • 私有成员的存在

缺点

  • 常驻内存,增加内存中使用量
  • 使用不当会很容易造成内存泄漏
function outer() {
    let name = 'mint';

    function inner() {
        console.log(name);
    }

    return inner
}
outer()();// mint

function sayHi(name) {
    return () => {
        console.log(`Hi! ${name}`);
    }
}

const test = sayHi("xiaoming");
test() // Hi! xiaoming

虽然sayHi函数以及执行完毕,但是起获得对象也不会被销毁,因为 test 函数仍然引用这 sayHi 函数中变量 name, 这就是闭包。但是因为闭包引用着另一个函数的变量,导致另一个函数已经不使用也无法销毁,所以闭包使用过,会占用较多的内存,这也是一个副作用。

解析

由于在 ECMA2015 中,只有函数才能分割作用域,函数内部可以访问当前作用域的变量,但是外部无法访问函数内部的变量,所以闭包可以理解成“定义在一个函数内部的函数,外部可以通过内部返回的函数访问内部函数的变量“。在本质上,闭包是将函数内部和函数外部连接起来的桥梁。

5.new 一个对象的过程中发生了什么

new 一个对象的四个过程:

function Person(name) {
    this.name = name;
}
let person = new Person("mint");

// 1.创建空对象
let obj = {}
//2.设置原型链:设置新对象的 constructor 属性为构造函数的名称,设置新对象的__proto__属性执行构造函数的 prototype 对象
obj.constructor = Person();
obj.__proto__ = Person.prototype;
// 3.改变this指向:使用新对象调用函数在的 this 指向新石烈对象 obj
let result = Person.call(obj)//{}构造函数()
//4.返回值:如果无返回值或一个非对象值,则将新对象返回;如果返回值是一个新对象的话,那么直接返回该对象
if (typeof(result) == "object") {
    person = result
} else {
    person = obj
}

6.for in 和 for of 的区别

for in

  • 一般用于遍历对象的可枚举属性。以及对象从构造函数原型中继承的属性。对于每个不同的属性,语句都会被执行

  • 不建议使用 for in 遍历数组,因为输出的顺序是不固定的

  • 如果迭代的对象的变量值是 null 或者 undefined, for in 不执行循环体,建议在使用 for in 循环之前,先检查该对象的值是不是 null 或者 undefined

*for of *

  • for…of 语句在可迭代对象(包括 Array,Map,Set,String,TypedArray,arguments 对象等等)上创建一个迭代循环,调用自定义迭代钩子,并为每个不同属性的值执行语句
let s = {
    a: 1,
    b: 2,
    c: 3
}

let  s1 = Object.create(s)

for (let prop in s1) {
    console.log(prop);//a b c
    console.log(s1[prop]); // 1 2 3
}
for (let prop of s1) {
    console.log(prop) //报错如下 Uncaught TypeError: s1 is not iterable
}

for (let prop of Object.keys(s1)) {
    console.log(prop); // a b c
    console.log(s1[prop]); //1 2 3
}

7.for in、Object. keys 和 Object. getOwnPropertyNames 对属性遍历有什么区别?

  • for in 会遍历自身及原型链上的可枚举属性
  • Object. keys 会将对象自身的可枚举属性的 key 输出
  • Object. getOwnPropertyNames 会将自身所有的属性的 key 输出

解析

ECMAScript 将对象的属性分为两种:数据属性和访问器属性。

// parent继承自Object.prototype,有一个可枚举的属性a(enumerable:true)。
let parent = Object.create(Object.prototype, {
    a: {
        value: 123,
        writable: true,
        enumerable: true,
        configurable: true
    }
})

//child 继承自 parent ,b可枚举,c不可枚举
let child = Object.create(parent, {
    b: {
        value: 2,
        writable: true,
        enumerable: true,
        configurable: true
    },
    c: {
        value: 3,
        writable: true,
        enumerable: false,
        configurable: true
    }
})

for in

for (let key in child) {
    console.log(key);
}

//b
//a
//for in 会遍历自身及原型链上的可枚举属性

如果只想输出自身的可枚举属性,可使用 hasOwnProperty 进行判断(数组与对象都可以,此处用数组做例子)

let arr = [1, 2, 3];
Array.prototype.xxx = 1231235;
for (let i in arr) {
    if (arr.hasOwnProperty(i)) {
        console.log(arr[i]);
    }
}
// 1
// 2
// 3

Object.keys

console.log(Object.keys(child));
// [ 'b' ]
// Object.keys 会将对象自身的可枚举属性的key输出

Object. getOwnPropertyNames

console.log(Object.getOwnPropertyNames(child));
//[ 'b', 'c' ]
// 会将自身所有的属性的key输出

8.Object. prototype. toString. call()和 instanceOf 和 Array. isArray() 区别好坏

Object. prototype. toString. call()

  • 优点:这种方法对于所有基本的数据类型都能进行判断,即使是 null 和 undefined 。
  • 缺点:不能精准判断自定义对象,对于自定义对象只会返回[object Object]

instanceOf

  • 优点:instanceof 可以弥补 Object. prototype. toString. call()不能判断自定义实例化对象的缺点。 缺点: instanceof 只能用来判断对象类型,原始类型不可以。并且所有对象类型 instanceof Object 都是 true,且不同于其他两种方法的是它不能检测出 iframes。

Array. isArray()

  • 优点:当检测 Array 实例时,Array. isArray 优于 instanceof ,因为 Array. isArray 可以检测出

  • iframes 缺点:只能判别数组

解析

Object. prototype. toString. call()

每一个继承 Object 的对象都有 toString 方法,如果 toString 方法没有重写的话,会返回 [Object type],其中type 为对象的类型。但当除了 Object 类型的对象外,其他类型直接使 toString 方法时,会直接返回都是内容的字符串,所以我们需要使用 call 或者 apply 方法来改变 toString 方法的执行上下文。

const an = ['Hello', 'An']
console.log(an.toString());// "Hello ,An"
console.log(Object.prototype.toString.call(an));// '[object Array]'

这种方法对于所有基本的数据类型都能进行判断,即使是 null 和 undefined 。

Object.prototype.toString.call("An"); // "[object String]"
Object.prototype.toString.call(1); // "[object Number]"
Object.prototype.toString.call(Symbol(1)); // "[object Symbol]"
Object.prototype.toString.call(null); // "[object Null]"
Object.prototype.toString.call(undefined); // "[object Undefined]"
Object.prototype.toString.call(function() {}); // "[object Function]"
Object.prototype.toString.call({
    name: "An"
}); // "[object Object]"

缺点:不能精准判断自定义对象,对于自定义对象只会返回[object Object]

function f(name) {
    this.name = name;
}
var f1 = new f("martin");
console.log(Object.prototype.toString.call(f1)); //[object Object]
Object.prototype.toString.call(); // 常用于判断浏览器内置对象。

instanceof

instanceof 的内部机制是通过判断对象的原型链中是不是能找到类型的 prototype。

使用 instanceof 判断一个对象是否为数组,instanceof 会判断这个对象的原型链上是否会找到对应的 Array 的 原型,找到返回 true,否则返回 false。

[] instanceof Array; // true

但 instanceof 只能用来判断对象类型,原始类型不可以。并且所有对象类型 instanceof Object 都是 true。

[] instanceof Object; // true

优点:instanceof 可以弥补 Object. prototype. toString. call()不能判断自定义实例化对象的缺点。

缺点:instanceof 只能用来判断对象类型,原始类型不可以。并且所有对象类型 instanceof Object 都是 true,且不同于其他两种方法的是它不能检测出 iframes。

function f(name) {
    this.name = name;
}
var f1 = new f("martin");
console.log(f1 instanceof f); //true

Array. isArray()

  • 功能:用来判断对象是否为数组
  • instanceof 与 isArray

当检测 Array 实例时,Array. isArray 优于 instanceof ,因为 Array. isArray 可以检测出 iframes

var iframe = document.createElement("iframe");
document.body.appendChild(iframe);
xArray = window.frames[ window.frames.length1].Array;
var arr = new xArray(1, 2, 3); // [1,2,3]
// Correctly checking for Array
Array.isArray(arr); // true
Object.prototype.toString.call(arr); // true
// Considered harmful, because doesn't work though iframes
arr instanceof Array; // false

缺点:只能判别数组

Array. isArray() 与 Object. prototype. toString. call().

Array. isArray()是 ES5 新增的方法,当不存在Array.isArray() ,可以用 Object. prototype. toString. call() 实现。

if (!Array.isArray) {
    Array.isArray = function(arg) {
        return Object.prototype.toString.call(arg) === "[object Array]";
    };
}

9.JS 单线程还是多线程,如何显示异步操作

JS 本身是单线程的,他是依靠浏览器完成的异步操作。

具体步骤:

  1. 主线程 执行 js 中所有的代码。

  2. 主线程 在执行过程中发现了需要异步的任务,任务后扔给浏览器(浏览器创建多个线程执行),并在 callback queue 中创建对应的回调函数(回调函数是一个对象,包含该函数是否执行完等)。

  3. 主线程 已经执行完毕所有同步代码。开始监听 callback queue 一旦 浏览器 中某个线程任务完成将会改变回调函数的状态。主线程查看到某个函数的状态为已完成,就会执行该函数。

image.png

10.JavaScript 数组的函数 map/forEach/reduce/filter

map

//作用:对数组进行遍历
//返回值:新的数组
// 是否改变:否
let arr = [2, 5, 3, 4]
let res = arr.map(function (value) {
    return value + 1
})
console.log(res);//[ 3, 6, 4, 5 ]
console.log(arr);//[ 2, 5, 3, 4 ]

forEach

// 作用:遍历数组的每一项
// 返回值:undefined
// 是否改变:否
let arr = [2, 5, 3, 4];
let res = arr.forEach(function (value) {
    console.log(value);
})
console.log(res); //undefined
console.log(arr); //[2,5,3,4]

reduce

// 作用:对数组进行迭代,然后两两进行操作,后返回一个值
// 返回值:return出来的结果
// 是否改变:不会
var arr = [1, 2, 3, 4];
var res = arr.reduce(function(a, b) {
 return a * b;
});
console.log(res); // 24
console.log(arr); // [1, 2, 3, 4]

filter

// 作用: 筛选一部分元素
// 返回值: 一个满足筛选条件的新数组
// 是否改变原有数组:不会
var arr = [2, 5, 3, 4];
var res = arr.filter(function(value) {
 return value > 3;
});
console.log(res); //[5,4]
console.log(arr); //[2,5,3,4]