前端面试必问问题18道---一篇文章进大厂

谈谈数组Array在项目中常见的场景
  • 求和,求最大(小)值,排序,多个数组合并(concat),去重,分割,find,indexOf,filter,join, toString等等
  • 数组的concat,join,slice,toString方法不会改变原数组
  • 数组的splice,push,pop,unshift,shift,sort,reverse方法会改变原数组
类数组怎么转化为数组

什么是类数组,就是属性要为索引(数字)属性,同时又必须有一个length属性来控制对象边界的一个特殊对象,特点:
1.不能使用数组的方法,他们不能使用Array的方法
2.拥有length属性,可以使用下标来访问元素,这两点和数组相同。

var obj = {
  '0': 'a',
  '1': 'b',
  '2': 'c',
  'length': 3
}

转化为数组的方法有3种:

  • 第一种,采用es6中的新方法Array.from()
var myArray = ["value1", "value2", "value3"];
var mySet = new Set(myArray);
console.log(Array.from(mySet));  //第一种方法Array.from()
console.log([...mySet]);         //第二种方法Array.from()
  • 第二种,创建新数组,通过forEach遍历把类数组的元素添加到新数组中

  • 第三种,通过Array.prototype.slice.call(arrayLike, 0)或者[].slice.call(arrayLike, 0)将类数组对象传入即可

  • 第四种,扩展运算符 ... [...arguments]

// 将参数转为数组
var fn = function() {
  console.log([...arguments]);
}
fn(1, 2, 3)

// 展开Set值
[...set.values()]
common.js和ES6模块化的区别

详情可见:
js的4种模块化的使用方法和区别
import、require、export、module.exports 混合使用详解

不同规范产生的来由啊,随着技术的发展,js这门语言模块化的需要也越来迫切,随之而来就是各种规范,什么CMD、AMD各种规范也应运而生,现在在服务端形成CommonJS规范,前端呢ES6模块化的概念也深入人心。
毕竟一个是用于后端,一个是用于前端,所以还是有很大的不同,比较一下common.js和ES6模块化的区别:

  • 前者支持动态导入,也就是 require(${path}/xx.js),后者目前不支持。

  • 前者是同步导入,因为用于服务端,文件都在本地,同步导入即使卡住主线程影响也不大。而后者是异步导入,因为用于浏览器,需要下载文件,如果也采用同步导入会对渲染有很大影响。

  • 前者在导出时都是值拷贝,就算导出的值变了,导入的值也不会改变,所以如果想更新值,必须重新导入一次。但是后者采用实时绑定的方式,导入导出的值都指向同一个内存地址,所以导入值会跟随导出值变化

  • require 在 ES6(bable将import转化为require) 和 CommonJS 中都支持,像Vue中一般引入CSS样式的时候会用到require,是因为样式一般需要同步导入的原因所致

  • 即使我们使用了 ES6 的模块系统,如果借助 Babel 的转换,ES6 的模块系统最终还是会转换成 CommonJS 的规范。

两个数据互换,比如a=6&b=5,怎么做能a=5&b=6
// 第一种方法
var a = 10;
var b = 20;
var tmp = a; // tmp = 10;
a = b;       // a = 20;
b = tmp;     // b = 10;
// 第二种方法
var a = 10;
var b = 20;
a = a + b;
b = a - b;
a = a - b;
TS中枚举的定义

枚举(Enum)类型用于取值被限定在一定范围内的场景,比如一周只能有七天,颜色限定为红绿蓝等,通过enum关键字定义

enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat};
console.log(Days[0] === 'Sun'); //true
console.log(Days['Sat'] === 6); //true
Set和Map

JavaScript的默认对象表示方式{}可以视为其他语言中的Map或Dictionary的数据结构,即一组键值对。

但是JavaScript的对象有个小问题,就是键必须是字符串。但实际上Number或者其他数据类型作为键也是非常合理的。为了解决这个问题,最新的ES6规范引入了新的数据类型Map和set.

  • Set [类数组] 集合是由一组无序且唯一(即不能重复)的项组成的,可以想象成集合是一个既没有重复元素,也没有顺序概念的数组
var a = new Set([1, 2, 3, {"1": "2"}, ["3","4"]])
  • Map [类对象] 它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键,是一种更完善的 Hash 结构实现。如果你需要“键值对”的数据结构,Map 比 Object 更合适
var m = new Map([['name', 'zhangsan'],['sex', 'male']]);
console.log(m); //Map {"name" => "zhangsan", "sex" => "male"}
防抖与节流的原理及实现

节流 (throttle) 先执行,过一段时间再执行第二次

function throttlePro(delay, action) {
    var tId;
    return function () {
        var context = this;
        var arg = arguments;
        if (tId) return;
        tId = setTimeout(function () {
            action.apply(context, arg);
            clearTimeout(tId);
            // setTimeout 返回一个整数,clearTimeout 之后,tId还是那个整数,setInterval同样如此
            tId = null;
        }, delay);
    }
}

去抖 (debounce) 直至事件结束后,在处理,比如resize/scroll

function debounce(delay, action) {
    var tId;
    return function () {
        var context = this;
        var arg = arguments;
        if (tId) clearTimeout(tId);
        tId = setTimeout(function () {
            action.apply(context, arg);
        }, delay);
    }
}
window.onscroll = debounce(1000, print);

实现原理:利用定时器setTimeout

展开(扩展)运算符 ...的使用场景
  • 替换Apply的函数调用
// 使用扩展运算符
var array1 = [1, 2, 3];
Math.min(...array1);
  • 数组合并
// 数组合并
var array1 = [1, 2, 3];
var array2 = [4, 5, 6];
var array3 = [...array1, ...array2, 7, 8]; //[1,2,3,4,5,6,7,8]
array1.push(...array2 )// 当然也可以使用concat等数组合并方法,但是扩展运算符提供了一种新的方式
  • 转换类数组为数组
// es5
var fn = function() {
  console.log(Array.prototype.slice.apply(arguments));
}
fn(1,2,3)

//扩展运算符
var fn = function() {
  console.log([...arguments]);
}
fn(1,2,3)
怎么拍平一个二维数组(数组的扁平化)

核心原因, 用Array.isArray判断每一项
js数组拍平(数组扁平化)的六种方式

怎么判断一个目标类型
  • typeof

    • 对于数组、函数、对象来说,其关系错综复杂,使用 typeof 都会统一返回 “object” 字符串,
    • null也会返回’object’
    • 对NaN返回是’number’
  • instanceof

    • 和typeof一样, [] instanceof Array[] instanceof Object 都是返回true
  • Object.prototype.toString.call()

Object.prototype.toString.call("jerry");    //[object String]
Object.prototype.toString.call(12);         //[object Number]
Object.prototype.toString.call(true));      //[object Boolean]
function Person(){};
Object.prototype.toString.call(new Person); //[object Object]
// 判断是否为函数
function isFunction(it) {
  return Object.prototype.toString.call(it) === '[object Function]';
}

// 判断是否为数组:
function isArray(o) {
  return Object.prototype.toString.call(o) === '[object Array]';
}
babel常用到的工具包
* babel: ES6转义的核心包
* babel-cli: 用于在终端使用babel,用命令行转码
* babel-core: 如果某些代码需要调用Babel的API进行转码,就要使用`babel-core`模块
* babel-loader: 执行转义的核心包
* babel-plugin-react-transform: 代替react-hot-loader的插件
* babel-preset-es2015: 现在被babel-preset-env取代了,被babel未来不会过时的(future-proof)”解决方案
* babel-preset-react: 转义react的jsx语法,
* babel-preset-stage-0: `stage-0`包含`stage-1`, `stage-2`以及`stage-3`的所有功能
Vue中一个页面能否有多个插槽,怎么区分

vue中的插槽(slot)
vue中的插槽,父组件向子组件传递填充的模板代码,子组件中提供给父组件使用的一个占位符,用<slot></slot>标签表示,比如HTML、组件等,填充的内容会替换掉子组件的<slot></slot>标签(替换占位符)。

如果需要多个占位符,则直接加name区分即可,即

// 子组件插槽
<div>
    <span>我是第一个插槽</span>
    <slot name="yanggb1"></slot>
    <span>我是第二个插槽</span>
    <slot name="yanggb2"></slot>
</div>

// 父组件插入模板
<template v-slot:yanggb1>
    <span>yanggb1</span>
</template>
<template v-slot:yanggb2>
    <span>yanggb2</span>
</template>
Vue中data为啥不直接返回一个对象,而要return出去

不使用return包裹的数据会在项目的全局可见,会造成变量污染,使用return包裹后数据中变量只在当前组件中生效,不会影响其他组件

函数作用域和函数执行顺序

作用域有全局变量局部变量(也称为函数作用域)两类, ES6新增块级作用域,通过新增命令letconst来体现

function func(args){
  if (true) {
    //let声明i
    let i = 6;
    //在if内打印i值
    console.log('inside: ' + i);//6
  }
  //在if外,再次打印i值
  console.log('outside: ' + i);//i is not defined
};

关于执行顺序,主要是由函数的定义方式中函数声明式定义 function student(){}所引起的变量置顶提升

聊聊new Function这个陌生面孔及函数作用域和函数执行顺序

js的深拷贝和浅拷贝

浅拷贝的时候如果数据是基本数据类型,那么就如同直接赋值那种,会拷贝其本身,如果除了基本数据类型之外还有一层对象,那么对于浅拷贝而言就只能拷贝其引用,对象的改变会反应到拷贝对象上;但是深拷贝就会拷贝多层,即使是嵌套了对象,也会都拷贝出来。

简单的拷贝方法(即对象中的值没有函数,数组,对象等复杂类型)

// 第一种 循环赋值
function simpleClone(initalObj) {
    var obj = {};
    for ( var i in initalObj) {
        obj[i] = initalObj[i];
    }
    return obj;
}

// 第二种 Object.assign
var obj2 = { a: 10, b: 20, c: 30 };
var cloneObj2 = Object.assign({}, obj2);
cloneObj2.b = 100;
console.log(obj2);
// { a: 10, b: 20, c: 30 } <-- 没被改到
console.log(cloneObj2);
// { a: 10, b: 100, c: 30 }

// 第三种 JSON.parse(JSON.stringify(obj1)) 一般处理json数据时才会使用
var obj1 = { body: { a: 10 } };
var obj2 = JSON.parse(JSON.stringify(obj1));

(终于弄清楚JS的深拷贝和浅拷贝了)[https://blog.csdn.net/QTFYING/article/details/90407551]

聊聊Promise

诞生的原因:在JavaScript的世界中,所有代码都是单线程执行的。由于这个“缺陷”,导致JavaScript的所有网络操作,浏览器事件,都必须是异步执行。异步执行可以用回调函数实现,最典型的就是ajax,最终被ES6纳入标准,生成Promise

判断下面函数的执行顺序

console.log(1)
setTimeout(()=>{console.log(2)},1000)
async function fn(){
    console.log(3)
    setTimeout(()=>{console.log(4)},20)
    return Promise.reject()
}
async function run(){
    console.log(5)
    await fn()
    console.log(6)
}
run()
//需要执行150ms左右
for(let i=0;i<90000000;i++){}
setTimeout(()=>{
    console.log(7)
    new Promise(resolve=>{
        console.log(8)
        resolve()
    }).then(()=>{console.log(9)})
},0)
console.log(10)

你真的了解Promise吗

闭包

定义:

  • 百度百科:
    闭包就是能够读取其他函数内部变量的函数。例如在javascript中,只有函数内部的子函数才能读取局部变量,所以闭包可以理解成“定义在一个函数内部的函数“。

  • 《JavaScript高级编程指南》:
    闭包是指有权访问另外一个函数作用域中的变量的函数。

  • MDN:
    闭包是由函数以及创建该函数的词法环境组合而成,这个环境包含了这个闭包创建时所能访问的所有局部变量

有什么作用呢:

  • 模仿块级作用域(匿名函数)
  • 储存变量,延长变量生命周期
  • 封装私有变量
  • 函数柯里化
// 正常正则验证字符串 reg.test(txt)

// 函数封装后
function check(reg, txt) {
    return reg.test(txt)
}

check(/\d+/g, 'test')       //false
check(/[a-z]+/g, 'test')    //true

// Currying后
function curryingCheck(reg) {
    return function(txt) {
        return reg.test(txt)
    }
}

var hasNumber = curryingCheck(/\d+/g)
var hasLetter = curryingCheck(/[a-z]+/g)

hasNumber('test1')      // true
hasNumber('testtest')   // false
hasLetter('21212')      // false

关于闭包的this指向

var name = "听风是风";
var obj = {
    name: "行星飞行",
    sayName: function () {
        var that = this;
        return function () {
            console.log(this.name); //this -> window -> 听风是风
            console.log(that.name);
        };
    }
};
obj.sayName()(); // 行星飞行

一篇文章看懂JS闭包,都要2020年了,你怎么能还不懂闭包?

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章