【前端面试分享】-2019“银十”面试题记录(上)

简介: 由于不可抗逆之因素,在金九银十的后半段开始求职。 面试的确可以驱动学习,驱动知识的归类整理。 以此文记录面试过程中遇到的题目,仅供分享,不喜勿喷。

前言



由于不可抗逆之因素,在金九银十的后半段开始求职。 面试的确可以驱动学习,驱动知识的归类整理。 以此文记录面试过程中遇到的题目,仅供分享,不喜勿喷。


js



js 事件循环


简述js事件循环?


简答:

首先,由于js是单线程,所有任务需要排队。又为了避免因IO慢等原因导致的阻塞,任务被分成了“同步任务sync”和“异步任务async”。


其次,

1.同步任务都在主线程上执行,形成一个“执行栈”(后进先出)。

2.主线程之外,还存在一个“任务队列”(先进先出)。异步任务有了运行结果后,就会将回调函数放置在任务队列中。

3.一旦调用栈清空,就会读取“任务队列”的回调函数到栈内等待主线程的执行

这是循环的三步骤。


然后,重点要说明的是,任务队列分为宏任务队列微任务队列

每当调用栈清空的时候,先去读取微任务队列的所有微任务(例:Promise.then),再去读取宏任务队列的宏任务(例:setTimeout)。


每执行完一个宏任务后,都需检查微任务队列是否为空,如果为空,则执行下一个宏任务,否则,将会先把微任务队列的微任务全部读取执行完。

宏任务:script,setTimeout,setImmediate,promise中的executor

微任务:promise.then,process.nextTick


进一步: 个人理解QAQ


去食堂打饭,只排了一队,窗口的阿姨问你要什么啊?


1.如果你只要一个叉烧和烧鸭的双拼饭(已经做好了放在窗口),阿姨直接打好饭递给了你,然后下一个;


2.如果你要腊肉炒饭(需要现场炒),阿姨会把这个需求告诉专门做炒饭的阿叔,然后让你在旁边等,不要影响下一位同学打饭;


3.阿叔把饭炒好了,递给了阿姨,阿姨此时正在给另外一个同学打双拼,她也会先把手上的双拼打完递给那位同学,才会把腊肉炒饭递给你;


4.但是辛苦的阿叔除了做炒饭之外呢,还需要做手抓饼。如果你不幸因为打赌输了需要帮傻_b舍友带一个或几个手抓饼。善良的阿叔会在炒完你的饭之后把手抓饼也一起给做了,再去做下一份炒饭。


这个场景里,打饭的队伍是“主线程”执行栈,打双拼饭是“同步任务”,做炒饭、做手抓饼是“异步任务”,做好的炒饭是“宏任务”,做好的手抓饼是“微任务”;


参考链接:

并发模型与事件循环

Tasks, microtasks, queues and schedules

一次弄懂Event Loop


推荐阅读:

Eventloop不可怕,可怕的是遇上Promise


js 原生手写


要求手写!(部分人(也就是我)从jquery开始写页面,再到用vue,往往会不重视或者已经忘记原生怎么写的了)


示例:

• document.querySelector("h2, h3").style.backgroundColor = "red";
• document.getElementById("demo");
• document.getElementsByTagName("P"); ......

参考链接:


js 原型链


简答:


为了实现“继承”这个常用面向对象语言中最基本的概念,javaScript 基于原型链做出了实现。

访问一个对象的属性时,先在基本属性中查找,如果没有,在沿着隐式原型_proto_这条链向上找


(因为obj._proto_===obj.constructor.prototype,对象的隐式原型_proto_等于构造这个对象的函数的显示原型prototype)


进一步:

1.object instanceof constructor ,instanceof 运算符用来检测 constructor.prototype 是否存在于参数 object 的原型链上

2.基本类型(Undefined、Null、Boolean、Number 和 String)、引用类型(Object、Array 和 Function)

3.一切(引用类型)都是对象,对象是属性的集合Object本质上是由一组无序的名值对组成的

4.对象都是通过函数创建的

5.每个函数function都有一个prototype,即原型。每个对象都有一个__proto__,可成为隐式原型,指向创建该对象的函数的prototype(这句话不理解,请先背下来)


Function.prototype.__proto__ === Object.prototype
obj.__proto__=== Object.prototype //Object.prototype
确是一个特例——它的__proto__指向的是null,切记切记!

几乎所有 JavaScript 中的对象都是位于原型链顶端的 Object 的实例。


参考链接:

深入理解javascript原型和闭包(6)——继承


闭包原理


简答:

闭包是函数和声明该函数的词法环境的组合。- MDN


在这样的词法环境下,阻止变量回收机制对变量的回收,可以访问函数内部作用域的变量。

function init() {
    var name = "Mozilla"; // name 是一个被 init 创建的局部变量
    function displayName() { // displayName() 是内部函数,一个闭包
        alert(name); // 使用了父函数中声明的变量
    }
    displayName();
}
init();


进一步

  • 回收机制
function a(){
    var i=0;
    function b(){
        alert(++i);
    }
    return b;
}
var c=a();
c();


在Javascript中,如果一个对象不再被引用,那么这个对象就会被GC回收。如果两个对象互相引用,而不再被第3者所引用,那么这两个互相引用的对象也会被回收。因为函数a被b引用,b又被a外的c引用,这就是为什么函数a执行后不会被回收的原因。(来源:百科)

  • 闭包优劣

优:

① 可以读取函数内部的变量

② 让这些变量的值始终保持在内存中,不会在f1调用后被自动清除。

劣:

① 使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题。


实际遇到

let r=[]
for (let i=0;i<5;i++) {
  let index=i+1
  let res_all=vm.$apid.allService(100,1,index).then((val)=>{//allService(100,1,1) size,current,serviceType
    let res=val.data.data.records
    r[i] = res.map(item => {
      return {
        name: item.serviceName,
        key: item.serviceId,
        icon: vm.$store.state.myBaseUrl+vm.$store.state.devUrl+item.imgUrl,
        cat: index
      }
    })
    console.log(r)
  })
}//循环请求异步问题


参考链接:

MDN-闭包


推荐阅读:


特注:闭包、作用域、原型链、js数据类型,将在学习过程中的某一个点融合在一起,这是js的“最基本”!


addEventListener 循环绑定


面试题:addEventListener 循环绑定,如何改进下面的代码能正确实现?有几种方法?


<a>1</a><a>2</a><a>3</a>
var elems = document.getElementsByTagName('a');
for (var i = 0; i < elems.length; i++) {
    elems[i].addEventListener('click', function (e) {
        e.preventDefault();
        alert('I am link #' + i);
    }, 'false');
};
// 这个代码是错误的,因为变量i从来就没被locked住
// 相反,当循环执行以后,我们在点击的时候i才获得数值
// 因为这个时候i操真正获得值
// 所以说无论点击那个连接,最终显示的都是I am link #3(如果有3个a元素的话)
解决1:用闭包解决
var elems = document.getElementsByTagName('a');
for (var i = 0; i < elems.length; i++) {
    elems[i].addEventListener('click', (function (num) {
        return function(e){
             e.preventDefault();
            alert('I am link #' + num);
        }
    })(i), 'false');
};
//因为i相对匿名函数是外面的变量,就把循环绑定的时候,将i的值传入到匿名函数内,
就可以了。因此需要在匿名函数(事件函数)外包裹一个匿名函数, 并立即执行。
解决2:把var换成let
var elems = document.getElementsByTagName('a');
for (let i = 0; i < elems.length; i++) {
    elems[i].addEventListener('click', function (e) {
        e.preventDefault();
        alert('I am link #' + i);
    }, 'false');
};
//var 命令的变量提升机制,var 命令实际只会执行一次。
//而 let 命令不存在变量提升,所以每次循环都会执行一次,声明一个新变量(但初始化的值不一样)。
//for 的每次循环都是不同的块级作用域,
//let 声明的变量是块级作用域的,所以也不存在重复声明的问题。
let生命变量的for循环里,每个匿名函数实际上引用的都是一个新的变量
解决3:没用到闭包解决
var elems = document.getElementsByTagName('a');
for (var i = 0; i < elems.length; i++) {
    elems[i].num = i;
    elems[i].addEventListener('click', function (e) {
        e.preventDefault();
        alert('I am link #' + this.num);
    }, 'false');
};


参考文献:

变量提升


面试题:


(function(){
    console.log(a)//undefined
    var a=1;
})()
(function(){
    console.log(a)//报错: Cannot access 'a' before initialization
    let a=1;
})()
(function(){
    {let a=1}
    console.log(a)//报错:a is not defined
})()


简答:

js 有变量提升和函数提升,指的是用 var声明变量 或用 function 函数名(){ } 声明的,会在 js预解析 阶段提升到顶端;(es6的let 和 const 不会提升)其次,函数提升优先级 高于 变量提升


webpack



webpack 打包原理


简答:


官网:

本质上,Webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。

当 Webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle


记忆点:静态模块打包器依赖关系图bundle

从前端工程化角度看(“前端工程化”是加分项):

Webpack等类似工具是前端工程化工具。前端工程化是把前端开发工作带入到更加系统和规范体系的一系列过程。这个过程会包括源代码的预编译、模块处理、代码压缩等构建方面的工作。

Webpack的“一切皆模块”以及“按需加载”两大特性使得它更好地服务于工程化。


记忆点:预编译代码压缩模块处理、、按需加载



进一步:

Webpack 主要在打包中处理了下面这些问题:
1.从入口文件开始分析整个应用的依赖树
2.将每个依赖模块包装起来,并放到一个数组中等待调用
3.实现模块加载的方法,并提供到模块执行的环境中,使得模块间可以互相调用
4.将执行入口文件的逻辑放在一个立即执行函数表达式中
复制代码
  • e.g.  webpack.config.js
module.exports = {
// 配置打包选项  development开发环境
mode: 'development', // production 生产环境
// 指定入口文件:要打包的文件
entry: './src/js/index.js',
// 指定输出文件:打包之后的文件
output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'main.min.js'
},
// 配置资源的加载器 loader
module: {
    rules: [
    // 配置js的加载器(把ES6转化为ES3/5代码)
    {
        test: /\.jsx?$/,
        loader: 'babel-loader',
        //打包除这个文件之外的文件
        exclude: path.join(__dirname, './node_modules'),
        //打包包括的文件
        include: path.join(__dirname, './src')
    },
    // 配置css的加载器
    {
        // 匹配.css结尾的文件
        test: /\.css$/,
        // 配置css文件的加载器,处理顺序:从右向左
        use: ['style-loader', 'css-loader']
    },
    // 配置less的加载器
    {
        test: /\.less$/,
        use: ['style-loader', 'css-loader', 'less-loader']
    }
    ]
},
// 配置插件
plugins: [
    new CleanWebpackPlugin(),
    // 动态生成html
    new HtmlWebpackPlugin({
    title: '测试标题',
    template: 'index.html'
    })
],
// 配置实时预览环境 
devServer: {
    contentBase: path.join(__dirname, 'dist'),
    port: 5000
}
}


参考链接:

webpack 中文文档-webpackjs.com

当我们谈论前端工程化时,我们谈论什么

webpack打包原理


推荐阅读:

[译] 愿未来没有 Webpack


webpack 配置多页面应用


简答:

用webpack构建多页应用可以有2种思路,

1.多页面单配置。主要把entry和plugins中的html-webpack-plugin进行改造即可。

2.多页面多配置。多页面单配置的优点在于,不同页面可以共享相同代码,容易实现长缓存。缺点主要是随着项目的日渐庞大,打包速度会有明显下降。

e.g. 多entry,修改plugin


var webpack = require('webpack');
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ExtractTextPlugin = require("extract-text-webpack-plugin");
const CleanWebpackPlugin = require('clean-webpack-plugin');
module.exports = {
    // 配置入口
    entry: {
        about: './src/pages/about/about.js',
        contact: './src/pages/contact/contact.js'
    },
    // 配置出口
    output: {
        path: __dirname + "/dist/",
        filename: 'js/[name]-[hash:5].js',
        publicPath: '/',
    },
    module: {
        loaders: [
            //解析.js
            {   
                test: '/\.js$/',  
                loader: 'babel',  
                exclude: path.resolve(__dirname, 'node_modules'), 
                include: path.resolve(__dirname, 'src'),
                query: {
                    presets: ['env'] 
                }
            },
            // css处理
            {
                test: /\.css$/,
                loader: 'style-loader!css-loader' 
            },
            // less处理
            { 
                test: /\.less$/,
                loader: 'style-loader!css-loader!less-loader'
            },
            // 图片处理
            { 
                test: /\.(png|jpg|gif|svg)$/,
                loader: 'file-loader',
                query: {
                    name: 'assets/[name]-[hash:5].[ext]'
                },
            },{
                test: /\.(htm|html)$/i,
                use:[ 'html-withimg-loader'] 
            }
        ]
    },
    plugins: [
        new ExtractTextPlugin(__dirname + '/assert/css/common.less'),
        //  minify:{
        //     removeComments: true,//删除注释
        //     collapseWhitespace:true//删除空格
        // }
        new HtmlWebpackPlugin({
            filename: __dirname + '/dist/about.html',
            inject: 'head',
            template: 'html-withimg-loader!'+__dirname + "/src/pages/about/about.html",
            chunks: ['about'],
            inlineSource: '.(js|css)$'
        }),
        new HtmlWebpackPlugin({
            inject: 'head',
            filename: __dirname + '/dist/contact.html',
            template: __dirname + "/src/pages/contact/contact.html",
            chunks: ['contact'],
            inlineSource: '.(js|css)$'
        }),
        //设置每一次build之前先删除dist
        new CleanWebpackPlugin(
            ['dist/*', 'dist/*',],&emsp;     //匹配删除的文件
            {
                root: __dirname,       &emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;//根目录
                verbose: true,        &emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;//开启在控制台输出信息
                dry: false        &emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;//启用删除文件
            }
        )
    ],
    // 起本地服务,我起的dist目录
    devServer: {
        contentBase: "./dist/",
        historyApiFallback: true,
        inline: true,
        hot: true,
        host: '192.168.1.107',//我的局域网ip
    }
}


进一步: 单页面应用与多页面应用的区别

  单页面应用(SinglePage Web Application,SPA) 多页面应用(MultiPage Application,MPA)
组成 一个外壳页面和多个页面片段组成 多个完整页面构成
资源共用(css,js) 共用,只需在外壳部分加载 不共用,每个页面都需要加载
刷新方式 页面局部刷新或更改 整页刷新
url 模式 a.com/#/pageone
a.com/#/pagetwo
a.com/pageone.html
a.com/pagetwo.html
用户体验 页面片段间的切换快,用户体验良好 页面切换加载缓慢,流畅度不够,用户体验比较差
转场动画 容易实现 无法实现
数据传递 容易 依赖 url传参、或者cookie 、localStorage等
搜索引擎优化(SEO) 需要单独方案、实现较为困难、不利于SEO检索 可利用服务器端渲染(SSR)优化 实现方法简易
试用范围 高要求的体验度、追求界面流畅的应用 适用于追求高度支持搜索引擎的应用
开发成本 较高,常需借助专业的框架 较低 ,但页面重复代码多
维护成本 相对容易 相对复杂


参考链接:

单页面应用和多页面应用对比分析

webpack4构建多页应用,了解一下


相关文章
|
22天前
|
缓存 前端开发 JavaScript
"面试通关秘籍:深度解析浏览器面试必考问题,从重绘回流到事件委托,让你一举拿下前端 Offer!"
【10月更文挑战第23天】在前端开发面试中,浏览器相关知识是必考内容。本文总结了四个常见问题:浏览器渲染机制、重绘与回流、性能优化及事件委托。通过具体示例和对比分析,帮助求职者更好地理解和准备面试。掌握这些知识点,有助于提升面试表现和实际工作能力。
58 1
|
2月前
|
Web App开发 前端开发 Linux
「offer来了」浅谈前端面试中开发环境常考知识点
该文章归纳了前端开发环境中常见的面试知识点,特别是围绕Git的使用进行了详细介绍,包括Git的基本概念、常用命令以及在团队协作中的最佳实践,同时还涉及了Chrome调试工具和Linux命令行的基础操作。
「offer来了」浅谈前端面试中开发环境常考知识点
|
3月前
|
存储 XML 移动开发
前端大厂面试真题
前端大厂面试真题
|
1月前
|
Web App开发 JavaScript 前端开发
前端Node.js面试题
前端Node.js面试题
|
3月前
|
存储 前端开发 JavaScript
44 个 React 前端面试问题
【8月更文挑战第18天】
52 2
|
3月前
|
存储 JavaScript 前端开发
2022年前端js面试题
2022年前端js面试题
39 0
|
3月前
|
存储 前端开发 JavaScript
44 个 React 前端面试问题
44 个 React 前端面试问题
|
3月前
|
存储 JavaScript 前端开发
|
3月前
|
Web App开发 存储 缓存
|
3月前
|
前端开发 应用服务中间件 API
"揭秘!面试官必问:你是如何巧妙绕过跨域难题的?前端代理VS服务器端CORS,哪个才是你的秘密武器?"
【8月更文挑战第21天】在软件开发中,尤其前后端分离架构下,跨域资源共享(CORS)是常见的挑战。主要解决方案有两种:一是服务器端配置CORS策略,通过设置响应头控制跨域访问权限,无需改动前端代码,增强安全性;二是前端代理转发,如使用Nginx或Webpack DevServer在开发环境中转发请求绕过同源策略,简化开发流程但不适用于生产环境。生产环境下应采用服务器端CORS策略以确保安全稳定。
50 0