前端面试题总结

目录

一、vue项目性能优化

二、vue3.0新特性

三、SPA单页面的理解

四、SPA的实现方式

五、对MVVM的理解

六、vue的响应式数据的理解

七、为什么利用多个域名来存储网站资源会更有效

八、src和href的区别

九、常见图片格式

十、一次js请求一般情况下有哪些地方会有缓存处理

十一、一个页面上有大量的图片,你有哪些方法优化这些图片的加载

十二、CSS 中可以通过哪些属性定义,使得一个 DOM 元素不显示在浏览器可视范围内? 

十三、超链接访问过后 hover 样式就不出现的问题是什么?如何解决? 

十四、CSS 中 link 和@import 的区别

十五、盒模型

十六、BFC是什么

十七、html语义化是什么

十八、HTML和XHTML的区别(w3c标准)

十九、typeof返回那些数据类型

二十、例举几种强制类型转换和隐式类型转换

二十一、call、apply、bind区别

二十二、阻止事件冒泡和默认事件

二十三、javascript同源策略

二十四、如何判断某变量是否为数组数据类型

二十五、javascript中实现继承的几种方式

二十六、闭包

二十七、http 中last-modified,cache-control,Expires

二十八、什么是sql注入,xss漏洞

二十九、用原生js实现map方法

三十、JS实现约瑟夫环(算法题)

三十一:冒泡排序(算法题)

三十二、快速排序(算法题)

三十三、var a = b = 5

三十四、一个页面从输入 URL 到页面加载显示完成,这个过程中都发生了什么?

三十五、给一个 dom 同时绑定两个点击事件,一个用捕获,一个用冒泡。会执行几次事件,会先执行冒泡还是捕获?

三十六、实现一个函数clone,可以对JavaScript中的5种主要的数据类型(包括Number、String、Object、Array、Boolean)进行值复制 

三十七、数组去重

三十八、函数声明与函数表达式的区别

三十九、typeof、instanceof 和 constructor

四十、AMD , CMD , CommonJs 和 ES6 对比

四十一、ES6新特性

四十二、js实现一个类

四十三、数组和对象有哪些原生方法

四十四、js对象的几种创建方式

四十五、原型、原型链

四十六、前端性能优化

四十七、JQ实现原理

四十八、promise理解

四十九、HTTP 协议和 HTTPS 区别

五十、简述 webpack 中的 loaders 与 plugin 的区别

五十一、webpack打包原理


一、vue项目性能优化

一、代码层面优化

1.v-if和v-show使用场景区分

v-if用于很少改变条件的场景,v-show用于频繁切换条件的场景

2.computed和watch使用场景区分

computed:计算属性,必须返回return,依赖其他属性值,且值有缓存,只有依赖的其他属性值改变才会重新获取

watch:监听,不用返回return,更多是监听某些数据的改变,触发某些方法和逻辑

3.v-for必须加key,且不可和v-if同级使用

必须加key是为了vue内部机制精确找到该条数据,数据更新时更快更好的diff

不可和v-if同级使用是因为v-for比v-if优先级更高,如果每一次都需要遍历整个数据,会影响速度

4.长列表性能优化

vue会通过Object.defineProperty进行数据劫持,实现视图响应数据的变化。若组件是纯粹的数据展示,不会有任何改变,则不需要数据劫持。可以通过Object.freeze冻结对象,不能被修改

export default {
    data() {
        return {
            users: {}
        }
    },
    async created() {
        const users = await axios.get("/api/getUsers")
        this.users = Object.freeze(users)
    }
}

5.事件销毁

组件内部若使用了addEventListener等方式增加了事件监听器,是不会自动销毁的,需要在组件销毁时手动移除,避免内存泄漏

created() {
    addEventListener('click', this.click, false)
},
beforeDestory() {
    removeEventListener('click', this.click, false)
}

6.图片懒加载

若图片过多,可以将页面内未出现在可视区域的图片先不做加载,待滚动到可视区域再去加载。在vue项目中可以使用vue-lazyload插件。

安装并在main.js中引用使用后,在vue文件中将img标签中的img属性改为v-lazy即可。

7.路由懒加载

由于vue是单页面应用,若路由引用过多,会导致使用webpack打包后的文件很大。进入项目首屏加载资源过多,导致白屏时间过长,不利于用户体验。

可以将不同路由对应的组件分隔成不同的代码块,然后当路由被访问时再加载对应组件。缺点是可能会使其他页面的打开速度降低,可以通过增加页面切换动画等方式优化交互。

const Foo = () => import('../Foo.vue')
const router = new VueRouter({
    route: [
        { path: '/foo', component: Foo }
    ]
})

注:在这一步可以通过添加webpackChunkName设置不同模块js的文件名。

const Foo = () => import(/* webpackChunkName: "foo" */ '../Foo.vue')
const router = new VueRouter({
    route: [
        { path: '/foo', component: Foo }
    ]
})

 再配合webpack.config.jsoutput.chunkFilename,我们可以设置打包生成的文件(chunk)的名字。(若webpack.config.js分环境分成了对应的webpack.uat.js、webpack.prod.js,同理在对应的文件设置即可)

module.exports = {
    entry:'./src/main.js', //入口文件
    output: {
        path: path.resolve(__dirname, 'dist'),
        chunkFilename: '[name].bundle.[contenthash:5].js', // [contenthash:5]的作用是生成的文件增加一个长度为5的hash值
        filename: 'bundle.js',
    }
}

这里设定了chunkFilename的命名规则为:[name]+.+bundle.[contenthash:5].js。这里的[name]就是/* webpackChunkName: "foo" */设定的值。 

8.第三方插件按需引入

在项目中若引入整个第三方插件,会导致项目的体积变大。可以使用babel-plugin-component,然后只需要按需引入即可。

9.优化无限列表性能

若项目存在非常长或者无线滚动的列表,可以使用vue-virtual-scroll-list 和 vue-virtual-scroller,只渲染少部分区域的内容,减少重新渲染组件和创建dom节点的时间。

10.服务端渲染SSR或预渲染

服务端渲染是指vue在客户端(浏览器)将标签渲染成整个html片段的工作放在服务端完成,服务端形成的html片段直接返回给客户端。

(1)优点:

更好的SEO:因为SPA(单页面)的内容是通过ajax获取,而搜索引擎爬取工具并不会等待ajax异步完成后再抓取页面内容,所以在SPA中是抓不到页面通过ajax获取到的内容;而SSR是直接由服务端返回已经渲染好的页面(数据已经包含在页面中),所以搜索爬取工具可以抓取渲染好的页面。

更好的内容到达时间(首屏加载更快):SPA会等待所有vue编译后的js文件都下载完成后才开始页面的渲染,文件下载等需要一定的时间,所以首屏渲染需要一定的时间;SSR直接由服务端渲染好页面直接返回显示,无需等待下载js文件及再去渲染等。

(2)缺点:

更多的开发条件限制:例如服务端渲染只支持beforeCreate和created两个钩子函数,这会导致一些外部扩展库需要特殊处理,才能在服务端渲染应用程序中运行;并且与可以部署在任何静态文件服务器上的完全静态单页面应用程序SPA不同,服务端渲染应用程序,需要处于Node.js server运行环境。

更多的服务器负载:在Node.js中渲染完整的应用程序,显然会比仅仅提供静态文件的server更加大量占用CPU资源。因此如果你预料在高流量环境中使用,请准备相应的服务器负载,并采用缓存策略。

二、webpack层面的优化

1.webpack对图片进行压缩

在vue项目中,首先可以在webpack.base.conf.jsurl-loader中设置limit大小来对图片处理,对小于limit的图片转为base64格式。对于较大的图片资源,可以使用image-webpack-loader来压缩图片。

2.减少ES6转ES5的冗余代码

babel插件会在将ES6转换成ES5代码时注入一些辅助函数,例如:

class HelloWebpack extends Component{...}

这段代码再被转换成能正常运行的ES5代码时需要以下两个辅助函数:

babel-runtime/helpers/createClass  // 用于实现 class 语法
babel-runtime/helpers/inherits  // 用于实现 extends 语法  

在默认情况下,babel会在每个输出文件中内嵌这些依赖的辅助函数代码,如果多个源代码文件都依赖这些辅助函数,那么这些辅助函数的代码将会出现很多次,造成代码冗余。为了不让这些辅助函数的代码重复出现,可以在依赖它们时通过require('babel-runtime/helpers/createClass')的方式导入,这样就能做到只让他们出现一次。babel-plugin-transform-runtime插件就是用来实现这个作用的,将相关辅助函数进行替换成导入语句,从而减少babel编译出来的代码的文件大小。

3.提取公共代码

如果项目中没有去将每个页面的第三方库和公共模块提取出来,则项目会存在以下问题:

  • 相同的资源被重复加载,浪费用户的流量和服务器的成本
  • 每个页面需要加载的资源太大,导致网页首屏加载缓慢,影响用户体验。

所以我们需要将多个页面的公共代码抽离成单独的文件,来优化以上问题。webpack内置了专门用于提取多个chunk中的公共部分的插件CommonsChunkPlugin,我们在项目中的配置如下:

// 所有在 package.json 里面依赖的包,都会被打包进 vendor.js 这个文件中。
new webpack.optimize.CommonsChunkPlugin({
  name: 'vendor',
  minChunks: function(module, count) {
    return (
      module.resource &&
      /\.js$/.test(module.resource) &&
      module.resource.indexOf(
        path.join(__dirname, '../node_modules')
      ) === 0
    );
  }
}),
// 抽取出代码模块的映射关系
new webpack.optimize.CommonsChunkPlugin({
  name: 'manifest',
  chunks: ['vendor']
})

4.模板预编译

当使用 DOM 内模板或 JavaScript 内的字符串模板时,模板会在运行时被编译为渲染函数。通常情况下这个过程已经足够快了,但对性能敏感的应用还是最好避免这种用法。

预编译模板最简单的方式就是使用单文件组件——相关的构建设置会自动把预编译处理好,所以构建好的代码已经包含了编译出来的渲染函数而不是原始的模板字符串。

如果你使用 webpack,并且喜欢分离 JavaScript 和模板文件,你可以使用 vue-template-loader,它也可以在构建过程中把模板文件转换成为 JavaScript 渲染函数。

5.提取组件的css

当使用单文件组件时,组件内的css会以style标签的方式通过js动态注入。这有一些小的运行开销,如果你使用服务端渲染,这会导致一段“无样式内容闪烁(fouc)”。将所有组件的css提取到同一个文件可以避免这个问题,也会让css更好的进行压缩和缓存。

注:

什么是FOUC(文档样式短暂失效)?
如果使用import方法对CSS进行导入,会导致某些页面在Windows 下的Internet Explorer出现一些奇怪的现象:以无样式显示页面内容的瞬间闪烁,这种现象称之为文档样式短暂失效(Flash of Unstyled Content),简称为FOUC。

原因大致为:
1,使用import方法导入样式表。
2,将样式表放在页面底部
3,有几个样式表,放在html结构的不同位置。

其实原理很清楚:当样式表晚于结构性html加载,当加载到此样式表时,页面将停止之前的渲染。此样式表被下载和解析后,将重新渲染页面,也就出现了短暂的花屏现象。

6.优化sourceMap

我们在项目进行打包后,会将开发中的多个文件代码打包到一个文件中,并且经过压缩、去掉多余的空格、babel编译化后,最终将编译得到的代码会用于线上环境,那么这样处理后的代码和源代码会有很大的差别,当有 bug的时候,我们只能定位到压缩处理后的代码位置,无法定位到开发环境中的代码,对于开发来说不好调式定位问题,因此 sourceMap 出现了,它就是为了解决不好调式代码问题的。

SourceMap 的可选值如下(+ 号越多,代表速度越快,- 号越多,代表速度越慢, o 代表中等速度 )

 

开发环境推荐: cheap-module-eval-source-map

生产环境推荐: cheap-module-source-map

原因如下:

  • cheap: 源代码中的列信息是没有任何作用,因此我们打包后的文件不希望包含列相关信息,只有行信息能建立打包前后的依赖关系。因此不管是开发环境或生产环境,我们都希望添加 cheap 的基本类型来忽略打包前后的列信息;

  • module :不管是开发环境还是正式环境,我们都希望能定位到bug的源代码具体的位置,比如说某个 Vue 文件报错了,我们希望能定位到具体的 Vue 文件,因此我们也需要 module 配置;

  • soure-map :source-map 会为每一个打包后的模块生成独立的 soucemap 文件 ,因此我们需要增加source-map 属性;

  • eval-source-map:eval 打包代码的速度非常快,因为它不生成 map 文件,但是可以对 eval 组合使用 eval-source-map 使用会将 map 文件以 DataURL 的形式存在打包后的 js 文件中。在正式环境中不要使用 eval-source-map, 因为它会增加文件的大小,但是在开发环境中,可以试用下,因为他们打包的速度很快。

7.构建结果输出分析

Webpack 输出的代码可读性非常差而且文件非常大,让我们非常头疼。为了更简单、直观地分析输出结果,社区中出现了许多可视化分析工具。这些工具以图形的方式将结果更直观地展示出来,让我们快速了解问题所在。接下来讲解我们在 Vue 项目中用到的分析工具:webpack-bundle-analyzer

我们在项目中 webpack.prod.conf.js 进行配置:

if (config.build.bundleAnalyzerReport) {
  var BundleAnalyzerPlugin =   require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
  webpackConfig.plugins.push(new BundleAnalyzerPlugin());
}

执行 $ npm run build --report 后生成分析报告。

三、基础的web技术优化

1.开启gzip压缩

2.浏览器缓存

3.cdn的使用

二、vue3.0新特性

1.监测机制的改变

3.0基于Proxy的observer实现,提供全语言覆盖的反应性跟踪,这消除了vue2中基于Object.defineProperty的实现所存在的很多限制:

只能检测属性,不能检测对象

检测属性的添加和删除

检测数组索引的长度的变更

支持Map、Set、WeakMap和WeakSet

2.模板

模板方面没有大的变更,只改了作用域插槽,2.x的机制导致作用域插槽变了,父组件会重新渲染,而3.0把作用域插槽改成了函数的方式,这样只会影响子组件的重新渲染,提升了渲染的性能。

同事,对于render函数的方面,vue3.0也会进行一系列更改来方便习惯直接使用api来生成vdom。

3.对象式的组件声明方式

vue2.x中的组件是通过声明的方式传入一系列option,和TS的结合需要通过一些装饰器的方式来做,虽然能实现功能,但是比较麻烦。3.0修改了组件的声明方式,改成了类式的写法,这样使得和TS的结合变得很容易。

此外,vue的源码也改用了TS来写。其实当代码的功能复杂之后,必须有一个静态类型系统来做一些辅助管理。现在vue3.0也全面改用TS来重写了,更是使得对外暴露的api更容易结合TS。静态类型系统对于复杂代码的维护确实很有必要。

三、SPA单页面的理解

SPA仅在web页面初始化时加载响应的html、js和css。一旦页面加载完成,SPA不会因为用户的操作而进行页面的重新加载或跳转,取而代之的是利用路由机制实现html内容变换,避免页面的重新加载。

优点:

  • 用户体验好、快,内容的改变不需要重新加载整个页面,避免了不必要的跳转和重复渲染;
  • 基于上面一点,SPA相对对服务器压力小;
  • 前后端职责分离,架构清晰,前端进行交互逻辑,后端负责数据处理

缺点:

  • 首屏加载慢:为实现单页web应用功能以及显示效果,需要再加载页面的时候将js、css统一加载,部分页面按需加载;
  • 不利于SEO:由于所有的内容都在一个页面中动态替换显示,所以在SEO上其有着天然的弱势

四、SPA的实现方式

前端路由实现原理:

  • hash模式
  • history模式

在hash模式中,在window上监听hashchange事件(地址栏中hash变化触发)驱动页面变化;

在history模式中,在window上监听popstate事件(浏览器的前进或后退按钮的点击触发)驱动界面变化,监听a链接点击事件用history.pushState、history.replaceState方法驱动界面变化

直接在界面用显示隐藏事件驱动界面变化。

五、对MVVM的理解

传统的服务端MVC架构模式:

models 数据模型,专门提供数据支持

controllers 控制器模块,处理不同的页面请求或者处理接口请求

views 视图文件

MVVM:

model 代表数据模式,也可以在model中定义数据修改和操作的业务逻辑

view 代表ui组件,负责将数据模型转化为ui展现出来

viewmodel 同步view和model的对象

理解:

  • 前端开发早期的时候都是操作dom
  • 后来使用JQ让我们提高了操作dom的效率,但从开发角度还是在大量的手动操作dom
  • mvvm模式让以往手动操作dom的方式彻底解脱了,它不要用户自己操作dom,而是将普通数据绑定带viewmodel上,会自动将数据渲染到页面中,视图变化会通知viewmodel层更新数据,数据变化也会通过viewmodel层更新视图,因此mvvm中最重要的角色就是viewmodel,真正意义上把视图和数据实现了解耦,提高了开发效率。

六、vue的响应式数据的理解

vue2.x使用Object.defineProperty

vue3.0使用ES6中新增的Proxy

  • 当你把一个普通的js对象传入vue实例作为data选项,vue会遍历此对象所有的property,并使用Object.defineProperty把这些property全部转为getter/setter。
  • 当使用这些数据属性时,会进行依赖收集(手机到当前组件的watcher)
  • 之后当依赖项的setter触发时,会通知watcher,从而使它关联的组件重新渲染

七、为什么利用多个域名来存储网站资源会更有效

  1. CDN缓存更方便
  2. 突破浏览器并发限制
  3. 节约cookie带宽
  4. 节约主域名的连接数,优化页面响应速度
  5. 防止不必要的安全问题

八、src和href的区别

src用于替换当前元素,href用于在当前文档和引用资源之间确立联系。

src指向外部资源的位置,指向的内容将会嵌入到文档中当前标签所在的位置;在请求src资源时会将其指向的资源下载并应用到文档内,例如js脚本,img图片和frame等元素。当浏览器解析到该元素时,会暂停其他资源的下载和处理,直到将该资源加载、编译、执行完毕,图片和框架等元素也是如此,类似于将所指向资源嵌入当前标签内。这也是为什么将js脚本放在底部而不是头部的原因。

href指向网络资源所在的位置,建立和当前元素(锚点)或当前文档(链接)之间的链接,如果我们再文档中添加

<link href=”common.css” rel=”stylesheet”/>

那么浏览器会识别该文档为css文件,就会并行下载资源并且不会停止对当前文档的处理。

九、常见图片格式

png-8、png-24、jpeg、gif、svg、webp

在质量相同的情况下,WebP 格式图像的体积要比 JPEG 格式图像小 40% (支持动态图)

十、一次js请求一般情况下有哪些地方会有缓存处理

dns缓存,cdn缓存,浏览器缓存,服务器缓存

十一、一个页面上有大量的图片,你有哪些方法优化这些图片的加载

  • 图片懒加载
  • 幻灯片、相册等,可以使用图片预加载技术,将当前展示图片的前一张和后一张优先下载
  • css图片可使用精灵图、iconfont、base64等技术
  • 若图片过大,可以使用特殊编码的图片,加载时会先加载一张压缩特别厉害的缩略图,以提高用户体验。

十二、CSS 中可以通过哪些属性定义,使得一个 DOM 元素不显示在浏览器可视范围内? 

display: none、visibility: hidden、设置宽高为0,设置透明度为0,设置z-index为负等

十三、超链接访问过后 hover 样式就不出现的问题是什么?如何解决? 


被点击访问过的超链接样式不在具有 hover 和 active 了,解决方法是改变 CSS 属性的排列顺序: L-V-H-A(link,visited,hover,active)

十四、CSS 中 link 和@import 的区别

link属于html标签,而@import是css中提供的

在页面加载的时候,link会同时被加载,而@import引用的css会在页面加载完成后才会加载引用的css

@import只有在ie5以上才可以被识别,而link是html标签,不存在浏览器兼容性问题

link引入样式的权重大于@import的引用

十五、盒模型

css盒模型有两种,ie盒模型和w3c盒模型

盒模型包括:内容、内边距(padding)、外边距(margin)、边框(border)

区别:w3c盒模型的width仅为内容的宽,ie盒模型的width包括内容的宽+内边距的宽*2

十六、BFC是什么

BFC,块级格式化上下文。一个创建了新的BFC的盒子是独立布局的,盒子内元素的布局不会影响盒子外的元素。

属于同一个BFC的两个相邻Box的margin会发生重叠。

十七、html语义化是什么

当页面样式加载失败的时候能够让页面呈现出清晰的结构

有利于seo优化,利于被搜索引擎收录

便于项目的开发以及维护,使html代码更具有可读性,便于其他设备解析

十八、HTML和XHTML的区别(w3c标准)

所有的标记都必须有对应的结束标记

所有标签的元素和属性名都必须小写

所有的元素都必须合理嵌套

所有的属性都必须用引号包裹

所有的<和&等特殊符号用编码表示

给所有的属性赋一个值

不要再注释内容中使用--

图片必须有说明文字

十九、typeof返回那些数据类型

object、string、Boolean、number、undefined、function

二十、例举几种强制类型转换和隐式类型转换

强制:String(),Number(),Boolean()、parseInt()等

隐式:++、==、+、-、+=、>、< 等

二十一、call、apply、bind区别

1、call,apply和bind的区别
它们在功能上是没有区别的,都是改变this的指向,它们的区别主要是在于方法的实现形式和参数传递上的不同。call和apply方法都是在调用之后立即执行的。而bind调用之后是返回原函数,需要再调用一次才行,
2、①:函数.call(对象,arg1,arg2....)
②:函数.apply(对象,[arg1,arg2,...])
③:var ss=函数.bind(对象,arg1,arg2,....)
3、总结一下call,apply,bind方法:
a:第一个参数都是指定函数内部中this的指向(函数执行时所在的作用域),然后根据指定的作用域,调用该函数。
b:都可以在函数调用时传递参数。call,bind方法需要直接传入,而apply方法需要以数组的形式传入。
c:call,apply方法是在调用之后立即执行函数,而bind方法没有立即执行,需要将函数再执行一遍。有点闭包的味道。
d:改变this对象的指向问题不仅有call,apply,bind方法,也可以使用that变量来固定this的指向。

二十二、阻止事件冒泡和默认事件

防止冒泡和捕获:w3c的方法是e.stopPropagation(),IE则是使用e.cancelBubble = true

function stopBubble(e) { 
//如果提供了事件对象,则这是一个非IE浏览器 
if ( e && e.stopPropagation ) 
    //因此它支持W3C的stopPropagation()方法 
    e.stopPropagation(); 
else 
    //否则,我们需要使用IE的方式来取消事件冒泡 
    window.event.cancelBubble = true; 
}

取消默认事件:w3c的方法是e.preventDefault(),IE则是使用e.returnValue = false;

//阻止浏览器的默认行为 
function stopDefault( e ) { 
    //阻止默认浏览器动作(W3C) 
    if ( e && e.preventDefault ) 
        e.preventDefault(); 
    //IE中阻止函数器默认动作的方式 
    else 
        window.event.returnValue = false; 
    return false; 
}

二十三、javascript同源策略

同源策略,即拥有相同的协议(protocol),端口(如果指定),主机(域名)的两个页面是属于同一个源。

然而在IE中比较特殊,IE中没有将端口号加入同源的条件中,因此端口不同那一项,在IE中是算同源的

二十四、如何判断某变量是否为数组数据类型

// 若不支持Array.isArray
if(typeof Array.isArray==="undefined") { 
  Array.isArray = function(arg){ 
        return Object.prototype.toString.call(arg)==="[object Array]" 
    };   
}
// 之后可通过Array.isArray判断

二十五、javascript中实现继承的几种方式

1.借助构造函数实现继承

//  定义父类
function Parent1 () {
    this.name = '陈楚',
    this.age = 18
}
//  定义子类
function Child1 () {
    //通过call()方法改变Child1的this指向使子类的函数体内执行父级的构造函数从而实现继承效果
    Parent1.call(this)
    this.address = '洪山区'
}
//  构建子类的实例s1
var s1 = new Child1()
console.log(s1.name)  //陈楚

通过上述栗子,我们好像确实让s1实例继承了父类Child1的name属性,达到了继承的效果,但是这种继承有一个很严重的缺点。构造函数继承法只能实现部分继承,如果我们在父类Parent1的原型链上添加属性或者方法的时候子类的实例无法继承到。 

//  父类添加say方法
Parent1.prototype.say = function () {
    console.log('say bye bye')
}
//  子类中直接打印这个say方法
console.log(s1.say())  //报错

2.借助原型链实现继承

function Parent2 () {
    this.name = '祝敏',
    this.age = 19,
    this.play = [1,2,3]
}
//  一样在父类添加say方法
Parent2.prototype = {
    say () {
        console.log('say bye bye')
    }
}
function Child2 () {
    this.address = '硚口区'
}
// 让子类的原型直接等于父类实例
Child2.prototype = new Parent2()
//  生成两个子类的实例s2、s3
var s2 = new Child2()
var s3 = new Child2()
// s2实例继承了父类中的name属性
console.log(s2.name)  //祝敏
//  s2实例也同样继承了父类原型上的say方法
console.log(s2.say())  //say bye bye

借助原型链实现继承虽然解决了父类原型的方法能让子类实例对象继承的问题,但是如果我们通过子类的实例对象修改父类上的属性和方法,那么所有子类的所有实例对象上的属性和方法都会被改变。

//  给s2实例继承的play属性的数组中push一个新数字
s2.play.push(4)
console.log(s2.play)  //[1, 2, 3, 4]
console.log(s3.play)  //[1, 2, 3, 4]

3.组合继承(完美版)

function Parent5 () {
    this.name = '许风',
    this.age = 20,
    this.play = [4,5,6]
}
function Child5 () {
    Parent5.call(this)
    this.address = '江夏区'
}
Child5.prototype = Object.create(Parent5.prototype)
Child5.prototype.constructor = Child5
var s9 = new Child5()
var s10 = new Parent5()
console.log(s9.constructor)  //指向构造函数Child5
console.log(s10.constructor)  //指向构造函数Parent5

二十六、闭包

「函数」和「函数内部能访问到的变量」(也叫环境)的总和,就是一个闭包。

具体可参考文章:https://zhuanlan.zhihu.com/p/22486908

二十七、http 中last-modified,cache-control,Expires

均可以提升前端性能。

last-modified:

通过HTTP 请求头中的 If-Modified-Since(If-No-Match) 和响应头中的Last-Modified(ETag)来实现,HTTP请求把 If-Modified-Since(If-No-Match)传给服务器,服务器将其与Last-Modified(ETag)对比,若相同,则文件没有被改动过,则返回304,直接浏览器缓存中读取资源即可。

问题:虽然该方法减少了已缓存资源的下载时间,但仍然发起了一次http请求。

Cache-Control:

Cache-Control属性值是在server端配置的,不同的服务器有不同的配置。可以设置服务器端文件缓存事件。若一些长期不变的图片比如logo,背景图片,字体,icon小图标等变化一般不会频繁,可以设的久一点。可以设个一年半载,具体设多久见仁见智了。新闻,广告等频繁更新的图片,不用缓存。css,js文件更新的周期会短一点,可以设置一周或者一个月。

如果用户浏览器缓存了页面的资源,你又想让用户更新怎么办呢?你可以通过修改该资源的名称来实现。名字改了,浏览器会当做不同的资源,这样就可以实现了。

expires:

expires也是需要在服务端配置(具体配置也根据服务器而定),expires添加的是该资源过期的日期,浏览器会根据该过期日期与客户端时间对比,如果过期时间还没到,则会去缓存中读取该资源,如果已经到期了,则浏览器判断为该资源已经不新鲜要重新从服务端获取。通过这种方式,可以实现直接从浏览器缓存中读取,而不需要去服务端判断是否已经缓存,避免了这次http请求。值得注意的是expires时间可能存在客户端时间跟服务端时间不一致的问题。所以,建议expires结合Cache-Control一起使用。

二十八、什么是sql注入,xss漏洞

Sql注入攻击原理:使用用户输入的参数拼凑sql查询语句,使用户可以控制sql查询语句。预防方法,使用预编译语句,绑定变量,使用安全的存储过程,检查数据类型,使用安全函数。

XSS(Cross Site Script)跨站脚本攻击,指恶意攻击者往web页面插入恶意脚本代码,而程序对于用户输入内容未过滤,当用户浏览该页面时,嵌入其中的web里面的脚本代码会被执行,从而达到恶意攻击用户的特殊目的。因此,一般在表单提交或者url参数传递前,对需要的参数进行过滤。

二十九、用原生js实现map方法

思路:
1.在原型上添加一个方法
2.传一个函数和this
3.call 方法传的参数和封装好的map方法的参数是一样的。

Array.prototype.fakeMap = function(fn,context) {
	let arr = this;
	let temp = [];
	for(let i=0;i<arr.length;i++){
		let result = fn.call(context,arr[i],i,arr);
		temp.push(result);
	}
	return temp;
}

三十、JS实现约瑟夫环(算法题)

n 个人围成一个圈,这 n 个人的编号从 0——(n-1), 第一个人(编号为0的人)从 1 开始报数,报数为 m 的人离开,再从下一个开始从 1 开始报数,报数为 m 的人离开,依次循环下去,直到剩下最后一个人(也可以剩最后两个,少循环一次就是了),那么,把最后一个人的编号打印出来。

function countOff(N, M) {
  if (N < 1 || M < 1) {
    return;
  }
  let source=[];
  for(let i=1;i<=N;i++){
    source.push(i);
  }
  // const source = Array(...Array(N)).map((_, i) => i + 1);
  let index = 0;
  while (source.length>1) {// 剩下一人,结束条件
    index = (index + M - 1) % source.length;
    console.log('出局:'+source[index]);
    source.splice(index, 1);
  }
  console.log('剩下:'+source[0])
}
countOff(100,5)

三十一:冒泡排序(算法题)

思路:a)比较两个相邻的元素,如果后一个比前一个大,则交换位置

         b) 第一轮的时候最后一个元素应该是最大的一个

         c) 按照第一步的方法进行两个相邻的元素的比较,由于最后一个元素已经是最大的了,所以最后一个元素不用比较。 

function sort(element){
        for(var i = 0;i<element.length-1;i++) {
            console.log("i="+element[i])
            for(var j = 0;j<element.length-i-1;j++){
                console.log("j="+element[j]);
                console.log("j+1="+element[j+1]);
                if(element[j]>element[j+1]){
                    //把大的数字放到后面
                    var swap = element[j];
                    element[j] = element[j+1];
                    element[j+1] = swap;
                }
            }
            console.log(element);
        }
    }
    var element = [3,5,1,2,7,8,4,5,3,4];
    //console.log("before:"+element);[3,5,1,2,7,8,4,5,3,4];
    sort(element);
    //console.log("after:"+element);[1, 2, 3, 3, 4, 4, 5, 5, 7, 8]

三十二、快速排序(算法题)

思路:

第一次排序的时候将数据分成两个部分,一部分比另外一部分所有的数据都要小,然后递归调用,在两边都实行快速排序。


    function quickSort(elements){
        console.log("元素"+elements);
        console.log("长度"+elements.length);
        if(elements.length<=1){
            return elements;
        }
        var index = Math.floor(elements.length/2);
        //console.log(index);//5->5->4->3->2->2->1
        var pivot = elements.splice(index,1)[0];//获取删除的数字
        //console.log("pivot="+pivot);//1-8-7-3-6-4.4-5.5
        var arrLeft = [];
        var arrRight = [];
        for(var i = 0;i<elements.length;i++){
            if(elements[i] < pivot){
                arrLeft.push(elements[i]);
                console.log("arrLeft="+arrLeft);
            }else{
                arrRight.push(elements[i]);
                console.log("arrright="+arrRight)
            }
        }
        return quickSort(arrLeft).concat([pivot],quickSort(arrRight));
      
    }
 
    var elements = [4,5,2,3,7,1,8,9,6,4.4,5.5];
    console.log(quickSort(elements));//[1, 2, 3, 4, 4.4, 5, 5.5, 6, 7, 8, 9]

三十三、var a = b = 5

        (function test() {
            var a = b = 5
            alert(typeof a)
            alert(typeof b)
        })();
        alert(typeof a);
        alert(typeof b);

答案是:number number undefind number

原因:

var a = b = 5
等价于

var a;
b = 5;
a = b;

b是全局变量,a是局部变量

三十四、一个页面从输入 URL 到页面加载显示完成,这个过程中都发生了什么?

1、浏览器地址栏输入url

2、浏览器会先查看浏览器缓存--系统缓存--路由缓存,如有存在缓存,就直接显示。如果没有,接着第三步

3、域名解析(DNS)获取相应的ip

4、浏览器向服务器发起tcp连接,与浏览器建立tcp三次握手

5、握手成功,浏览器向服务器发送http请求,请求数据包

6、服务器请求数据,将数据返回到浏览器

7、浏览器接收响应,读取页面内容,解析html源码,生成DOm树

8、解析css样式、浏览器渲染,js交互绑定多个域名,数量不限;

三次握手:作用于传输层的TCP协议向远端服务器发起连接请求

①源端->远端:你好,我想跟你连接可以吗?(SYN=1,seq=x)

②远端->源端:可以,你确定要连接是吧?(SYN=1,ACK=1,seq=y,ack=x+1)

③源端->远端:确定,我们连接吧!(ACK=1,seq=x+1,ack=y+1)

注:为什么需要三次握手,两次不行吗?

弄清这个问题,我们需要先弄明白三次握手的目的是什么,能不能只用两次握手来达到同样的目的。

第一次握手:客户端发送网络包,服务端收到了。

这样服务端就能得出结论:客户端的发送能力、服务端的接收能力是正常的。

第二次握手:服务端发包,客户端收到了。

这样客户端就能得出结论:服务端的接收、发送能力,客户端的接收、发送能力是正常的。不过此时服务器并不能确认客户端的接收能力是否正常。

第三次握手:客户端发包,服务端收到了。

这样服务端就能得出结论:客户端的接收、发送能力正常,服务器自己的发送、接收能力也正常。

因此,需要三次握手才能确认双方的接收与发送能力是否正常。

试想如果是用两次握手,则会出现下面这种情况:

如客户端发出连接请求,但因连接请求报文丢失而未收到确认,于是客户端再重传一次连接请求。后来收到了确认,建立了连接。数据传输完毕后,就释放了连接,客户端共发出了两个连接请求报文段,其中第一个丢失,第二个到达了服务端,但是第一个丢失的报文段只是在某些网络结点长时间滞留了,延误到连接释放以后的某个时间才到达服务端,此时服务端误认为客户端又发出一次新的连接请求,于是就向客户端发出确认报文段,同意建立连接,不采用三次握手,只要服务端发出确认,就建立新的连接了,此时客户端忽略服务端发来的确认,也不发送数据,则服务端一致等待客户端发送数据,浪费资源。

四次挥手:断开TCP连接。(连接的主动断开是可以发生在客户端,也同样可以发生在服务端。)

①源端->远端:好了,咱们断开吧(FIN=1,seq=u)

②远端->源端:行,等我稍微检查一下还有没有要发你的数据(ACK=1,seq=v,ack=u+1)

③远端->源端:可以了,咱们断开吧,拜拜(FIN=1,ACK=1,seq=w,ack=u+1)

④源端->远端:好的,再会,拜拜(ACK=1,seq=u+1,ack=w+1)

注:为什么建立一个连接需要三次握手,而终止一个连接要经过四次挥手?

这由TCP的半关闭(half-close)造成的。所谓的半关闭,其实就是TCP提供了连接的一端在结束它的发送后还能接收来自另一端数据的能力。

注:挥手为什么需要四次?

因为当服务端收到客户端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当服务端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉客户端,“你发的FIN报文我收到了”。只有等到我服务端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四次挥手。

三十五、给一个 dom 同时绑定两个点击事件,一个用捕获,一个用冒泡。会执行几次事件,会先执行冒泡还是捕获?

// useCapture是事件冒泡,还是事件捕获,默认false,代表事件冒泡类型

addEventListener(type, listener, useCapture);

var dom = document.getElementById("outestA"); 
dom.addEventListener('click', a, false); 
dom.addEventListener('click', a, true); 
   
function a() 
{  
  alert(this.id);//outestA 
}// 当点击outestA的时候,函数a会调用2次

执行两次。addEventListener和attachEvent表现一致,如果给同一个事件绑定多个处理函数,先绑定的先执行。

三十六、实现一个函数clone,可以对JavaScript中的5种主要的数据类型(包括Number、String、Object、Array、Boolean)进行值复制 

function clone(obj) {
  let ret
  // 必须先判断Array类型,后判断Object类型
  // 因为 [] instanceof Object === true
  if (obj instanceof Array) {
    ret = []
    for (let index = 0; index < obj.length; index++) {
      ret[index] = clone(obj[index])
    }
  } else if (obj instanceof Object) {
    ret = {}
    for (const key in obj) {
      if (Object.hasOwnProperty.call(obj, key)) {
        ret[key] = clone(obj[key])
      }
    }
  } else {
    ret = obj
  }
  return ret
}

三十七、数组去重

详见:https://segmentfault.com/a/1190000016418021?utm_source=tag-newest

三十八、函数声明与函数表达式的区别

在js中,解析器在向执行环境中加载数据时,对函数声明和函数表达式并非是一视同仁的。解析器会率先读取函数声明,并使其在执行任何代码之前可用,至于函数表达式,则必须等到解析器执行到它所在的代码行,才会真正被解析执行。

//函数声明式
function greeting(){
      console.log("hello world");  
}

//函数表达式
var greeting = function(){
    console.log("hello world"); 
}

三十九、typeof、instanceof 和 constructor

typeof、instanceof和constructor都可以用于识别正在处理的对象的类型。

typeof:

对变量或值调用 typeof 运算符将返回下列值之一:

undefined - 如果变量是 Undefined 类型的
boolean - 如果变量是 Boolean 类型的
number - 如果变量是 Number 类型的
string - 如果变量是 String 类型的
object - 如果变量是一种引用类型或 Null 类型的

在使用 typeof 运算符时采用引用类型存储值会出现一个问题,无论引用的是什么类型的对象,它都返回 "object"。

ECMAScript 引入了另一个 Java 运算符 instanceof 来解决这个问题。


instanceof
与 typeof 方法不同的是,instanceof 方法要求明确地确认对象为某特定类型。


constructor 

constructor 属性返回对创建此对象的数组函数的引用。

constructor 可以是函数的,也可以是对象的,不过都是指向函数的一个引用。
一般函数定义的时候,默认有一个属性prototype.constructor

var xx = "good" ;
typeof xx ;// string
xx instanceof  Stirng //false ,注意instance 只对用new生成(对象)的才有效
xx.constructor == String //true
xx.constructor.prototype.constructor == String //true
 
var xx =new Array(11,12,13)  ;
typeof xx ;//object
xx instanceof  Array //true 
xx.constructor == Array //true
xx.constructor.prototype.constructor == Array //true
xx instanceof  Object //true 
xx.constructor == Object //false
xx.constructor.prototype.constructor == Object //false

四十、AMD , CMD , CommonJs 和 ES6 对比

详见:https://blog.csdn.net/only_neo/article/details/115378509

四十一、ES6新特性

  • const 与 let 变量
  • 模板字符串
  • 解构赋值
  • 对象字面量简写
  • for...of循环
  • 展开运算符
  • 箭头函数
  • 函数默认参数
  • class类等

四十二、js实现一个类

es6:

//定义类
class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  toString() {
    return '(' + this.x + ', ' + this.y + ')';
  }
}

es5:

function Point(x, y) {
  this.x = x;
  this.y = y;
}

Point.prototype.toString = function () {
  return '(' + this.x + ', ' + this.y + ')';
};

var p = new Point(1, 2);

详见:http://caibaojian.com/es6/class.html

四十三、数组和对象有哪些原生方法

数组:

  • Array.concat( ) 连接数组  
  • Array.join( ) 将数组元素连接起来以构建一个字符串  
  • Array.length 数组的大小  
  • Array.pop( ) 删除并返回数组的最后一个元素  
  • Array.reverse( ) 颠倒数组中元素的顺序  
  • Array.shift( ) 将元素移出数组  
  • Array.slice( ) 返回数组的一部分  
  • Array.sort( ) 对数组元素进行排序  
  • Array.splice( ) 插入、删除或替换数组的元素  
  • Array.toLocaleString( ) 把数组转换成局部字符串  
  • Array.toString( ) 将数组转换成一个字符串
  • Array.unshift( ) 在数组头部插入一个元素 
  • Array.push( ) 给数组添加元素
  • Array.map() 遍历
  • Array.forEach()遍历
  • Array.filter()过滤
  • Array.reduce() 遍历
  • ...

对象:

 

  • Object.hasOwnProperty( ) 检查属性是否被继承  
  • Object.isPrototypeOf( ) 一个对象是否是另一个对象的原型  
  • Object.propertyIsEnumerable( ) 是否可以通过 for/in 循环看到属性  
  • Object.toLocaleString( ) 返回对象的本地字符串表示  
  • Object.toString( ) 定义一个对象的字符串表示  
  • Object.valueOf( ) 指定对象的原始值 
  • ...

四十四、js对象的几种创建方式

第一种:Object构造函数创建

var Person = new Object();
Person.name = 'Nike';
Person.age = 29;

第二种:使用对象字面量表示法

var Person = {};//相当于var Person = new Object();
var Person = {
 name:'Nike';
 age:29;  
}

第三种:使用工厂模式创建对象

function createPerson(name,age,job){
 var o = new Object();
 o.name = name;
 o.age = age;
 o.job = job;
 o.sayName = function(){
  alert(this.name); 
 };
 return o; 
}
var person1 = createPerson('Nike',29,'teacher');
var person2 = createPerson('Arvin',20,'student');

第四种:使用构造函数创建对象

function Person(name,age,job){
 this.name = name;
 this.age = age;
 this.job = job;
 this.sayName = function(){
 alert(this.name);
 }; 
}
var person1 = new Person('Nike',29,'teacher');
var person2 = new Person('Arvin',20,'student');

第五种:原型创建对象模式

function Person(){}
Person.prototype.name = 'Nike';
Person.prototype.age = 20;
Person.prototype.jbo = 'teacher';
Person.prototype.sayName = function(){
 alert(this.name);
};
var person1 = new Person();
person1.sayName();

第六种:组合使用构造函数模式和原型模式

function Person(name,age,job){
 this.name =name;
 this.age = age;
 this.job = job;
}
Person.prototype = {
 constructor:Person,
 sayName: function(){
 alert(this.name);
 };
}
var person1 = new Person('Nike',20,'teacher');

四十五、原型、原型链

1.谈谈你对原型的理解

在 JavaScript 中,每当定义一个对象(函数也是对象)时候,对象中都会包含一些预定义的属性。其中每个函数对象都有一个prototype 属性,这个属性指向函数的原型对象,使用原型对象的好处是所有对象实例共享它所包含的属性和方法。

2.什么是原型链?原型链解决的是什么问题

1)原型链解决的主要是继承问题
2)每个对象拥有一个原型对象,通过 proto 指针指向其原型对象,并从中继承方法和属性,同时原型对象也可能拥有原型,这样一层一层,最终指向 null(Object.proptotype.__proto__指向的是null)。这种关系被称为原型链(prototype chain),通过原型链一个对象可以拥有定义在其他对象中的属性和方法
3)构造函数 Parent、Parent.prototype 和 实例 p 的关系如下:(p.__proto__ === Parent.prototype)

3.prototype 和 proto 区别是什么

1)prototype是构造函数的属性
2)__proto__是每个实例都有的属性,可以访问 [[prototype]] 属性
3)实例的__proto__与其构造函数的prototype指向的是同一个对象

四十六、前端性能优化

  1.  减少 http 请求次数:CSS Sprites, JS、CSS 源码压缩、图片大小控制合适;网页Gzip,CDN 托管,data 缓存 ,图片服务器。 
  2.  前端模板 JS+数据,减少由于 HTML 标签导致的带宽浪费,前端用变量保存 AJAX请求结果,每次操作本地变量,不用请求,减少请求次数 
  3.  用 innerHTML 代替 DOM 操作,减少 DOM 操作次数,优化 javascript 性能。 
  4. 当需要设置的样式很多时设置 className 而不是直接操作 style。 
  5. 少用全局变量、缓存 DOM 节点查找的结果。减少 IO 读取操作。 
  6. 避免使用 CSS Expression(css 表达式)又称 Dynamic properties(动态属性)。 
  7. 图片预加载,将样式表放在顶部,将脚本放在底部  加上时间戳。 
  8. 避免在页面的主体布局中使用 table,table 要等其中的内容完全下载之后才会显示出来,显示比 div+css 布局慢。 

四十七、JQ实现原理

1. jq利用自执行函数,将所有的实现细节封装在自执行函数内,对外仅仅暴露jQuery和$符号:

    (function(w, u) {
    	"use strict";
    	w.jQuery = w.$ = jQuery;
    	var a;
    	if (a == u)
    		return;
    })(window);

2. 在jq中我们使用选择器,是直接使用$(selector)的,但是其实他返回的是一个jq对象,其中挂载了很多jq方法。

    (function(window, undefined){
        jQuery = function(selector, context){
            return new jQuery.fn.init(selector, context);
        },
        
        jQuery.fn = jQuery.prototype = {
            init: function(selector, context, rootjQuery){
                // ...
            }
        },
        
        
        jQuery.fn.init.prototype = jQuery.fn;
    })(window);

当我们传入一个选择器时,会返回new jQuery.fn.init(selector, context);而当我们传入空选择器时,在init中会显示返回this,此时this是指带init,而init的prototype被赋值为jQuery.fn,因此就相当于jQuery的实例。

  • jQuery.fn.init.prototype = jQuery.fn = jQuery.prototype
  • new jQuery.fn.init() 相当于 new jQuery() ;

3. jq链式调用原理

每次函数执行完的时候都会返回this,jq对象本身

4.我们的jQuery不仅仅是一个类(在它的原型上定义了很多的方法,每一个jQuery的实例都可以使用这些方法),它还是一个普通的对象,在jQuery本身的属性中还增加了一系列的方法:Ajax、each、工具,如:

  • $.unique(ary)
  • $.ajax()

自己实现一个示例:

function jQuery(selector) {
    this.elements = []
    this.querySelectorAll(selector)
}

jQuery.prototype = {
    querySelectorAll(selector) {
        this.elements = document.querySelectorAll(selector)
        return this
    },
    attr(key, value) {
        /**
         * 第一种情况 只有key
         * 也就是取元素的属性值
         * 取值就返回属性值 不需要返回this
         */
        if ( value === void 0 ) {
            var el = this.elements[0]
            return el && el.getAttribute(key)
        }

        /**
         * 第二种情况 key + value
         * 也就是给元素添加属性
         * 返回this
         */
        this.elements.forEach(function(el) {
            el.setAttribute(key, value)
        })
        return this
    }
}

window.$ = function(selector) {
    return new jQuery(selector)
}

/**
 * 使用
 */
$('div').attr('a', 1).attr('b', 2).attr('c', 3)

四十八、promise理解

一、什么是 Promise?

我们都知道,Promise 是承诺的意思,承诺它过一段时间会给你一个结果。 Promise 是一种解决异步编程的方案,相比回调函数和事件更合理和更 强大。 从语法上讲,promise 是一个对象,从它可以获取异步操作的消息;

二、promise 有三种状态:

pending 初始状态也叫等待状态,fulfiled 成功状态,rejected 失败状态;状态一旦改变,就不会再变。创造 promise 实例后,它会立即执行。

三、Promise 的两个特点

1、Promise 对象的状态不受外界影响

2、Promise 的状态一旦改变,就不会再变,任何时候都可以得到这个结果,状态不可以逆

四、Promise 的三个缺点

1)无法取消 Promise,一旦新建它就会立即执行,无法中途取消

2)如果不设置回调函数,Promise 内部抛出的错误,不会反映到外部

3)当处于 pending(等待)状态时,无法得知目前进展到哪一个阶段, 是刚刚开始还是即将完成

五、用来解决什么问题

1.回调地狱,代码难以维护, 常常第一个的函数的输出是第二个函数的输入这种现象

2.promise 可以支持多并发的请求,获取并发请求中的数据。

这个 promise 可以解决异步的问题,本身不能说 promise 是异步的

四十九、HTTP 协议和 HTTPS 区别

http 是超文本传输协议,信息是明文传输

https 是具有安全性的 ssl 解密传输协议。

http 和 https 连接方式完全不同,端口也不同。

http 是 80,https 是 443 http 的连接很简单,是无状态的

https 协议是由 ssl+http 协议构建的 可进行加密传输,身份认证的网络协议,比 http 协议安全

五十、简述 webpack 中的 loaders 与 plugin 的区别

什么是 loaders?loaders 是文件加载器,能够加载资源文件,并对这些文件进行处理,例如,编译,压缩等,最终一起打包到指定文件中。

什么是 plugin?在 webpack 运行的生命周期会有许多事件,plugin可以监听这些事件

区别:

加载器是用来加载文件的,webpack 本身只能加载 js 文件(内 置 babel-loader),加载其他文件就需要安装别的 loader,比如: css-loader file-loader Plugin 是扩展 webpack 功能的。通过 plugin ,webpack 可以实现 loader 不能完成的复杂功能

五十一、webpack打包原理

webpack 是把项目当做一个整体,通过给定一个主文件,webpack 将从这个主文件开始找到项目中所有依赖的文件,使用 loaders 类处理,最后打包成一个或者多个浏览器可识别的 js 文件。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Neo 丶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值