2023收藏的面试题
new操作符具体干了什么
1.先创建了一个新的对象newObj
2.将新对象newObj与构造函数通过原型链进行连接
3.将构造函数的this绑定到新对象newObj
4.根据构建函数返回类型作判断,如果是值类型,返回newObj。如果是引用类型,就返回这个引用类型的对象
function newFunc(Func,...args) {
// 1.创建一个新对象
let newObj = {}
// 2.将新对象和构造函数通过原型链连接
newObj.__proto__ = Func.prototype
// 3.将构造函数的this绑定到新对象上
const result = Func.apply(newObj,args)
// 4.根据返回值类型判断,如果是值类型返回newObj,如果是引用类型返回正常引用类型
return result instanceof Object ? result : newObj
}
typescript和javascript区别
1.TypeScript 是 JavaScript 的超集,TypeScript 遵循强类型,如果将不同的类型赋值给变量会编译错误,而JavaScript则不会,因为她是弱类型语言,
2.用TypeScript编写的代码首先需要编译,然后转换为JavaScript。此转换过程称为转译。3.typescript可以在编译期间发现并纠正错误。javascript只能在运行时发现错误
4.ts增加了接口interface、泛型、类、类的多态、继承等
重绘和重排
重绘是一个元素外观的改变所触发的浏览器行为,例如改变vidibility、outline、背景色等属性。浏览器会根据元素的新属性重新绘制,使元素呈现新的外观。重绘不会带来重新布局,并不一定伴随重排。
重排是更明显的一种改变,可以理解为渲染树需要重新计。
重排发生条件:
1.添加或删除可见的DOM元素
2.元素位置的改变
3.元素尺寸改变
4.内容改变
5.页面渲染器初始化
6.浏览器窗口尺寸的改变
重排进行优化:
- 将多次改变样式属性的操作合并成一次操作
- 将需要多次重排的元素,position属性设为absolute或fixed,这样此元素就脱离了文档流,它的变化不会影响到其他元素。例如有动画效果的元素就最好设置为绝对定位。
- 在内存中多次操作节点,完成后再添加到文档中去。例如要异步获取表格数据,渲染到页面。可以先取得数据后在内存中构建整个表格的html段,再一次性添加到文档中去,而不是循环添加每一行。
- 由于display属性为none的元素不在渲染树中,对隐藏的元素操作不会引发其他元素的重排。如果要对一个元素进行复杂的操作时,可以先隐藏它,操作完成后再显示。这样只在隐藏和显示时触发2次重排。
闭包:
当一个函数的返回值是另外一个函数,而返回的那个函数如果调用了其父函数的内部变量,且返回的那个函数在外部被执行,就产生了闭包.
特点:
1.通过闭包可以让外部环境访问到函数内部的局部变量。
2.通过闭包可以让局部变量持续保存下来,不随着它的上下文环境一起销毁。
使用场景:
- 防抖节流函数
- 单例模式
- 实现柯里化
- Vue中数据响应式Observer中使用闭包等。
防抖怎么做:
function debounce(fn, delay){
let timer = null;
return function(){
clearTimeout(timer);
timer = setTimeout(()=> {
fn.apply(this, arguments);
}, delay)
}
}
节流怎么做:
function throttle(fn, delay){
let valid = true;
return function(){
if(valid) { //如果阀门已经打开,就继续往下
setTimeout(()=> {
fn.apply(this, arguments);//定时器结束后执行
valid = true;//执行完成后打开阀门
}, delay)
valid = false;//关闭阀门
}
}
}
vue-router 中常用的 hash 和 history 路由模式
hash 模式的实现原理:
使用 JavaScript 来对 loaction.hash 进行赋值,改变 URL 的 hash 值,使用 hashchange 事件来监听 hash 值的变化,即使前端并没有发起http请求他也能够找到对应页面的代码块进行按需加载。
history 模式的实现原理:
history.pushState() 和 history.repalceState()。这两个 API 可以在不进行刷新的情况下,操作浏览器的历史纪录。
唯一不同的是,前者是新增一个历史记录,后者是直接替换当前的历史记录
SPA 单页面的理解
SPA( single-page application )仅在 Web 页面初始化时加载相应的 HTML、JavaScript 和 CSS。页面加载完成,SPA 不会因为用户的操作而进行页面的重新加载或跳转;取而代之的是利用路由机制实现 HTML 内容的变换,UI 与用户的交互,避免页面的重新加载。
优点:
用户体验好、快,内容的改变不需要重新加载整个页面基于上面一点,SPA 相对对服务器压力小;
前后端职责分离,架构清晰,前端进行交互逻辑,后端负责数据处理;
缺点:
初次加载耗时多:为实现单页 Web 应用功能及显示效果,需要在加载页面的时候将 JavaScript、CSS 统一加载,部分页面按需加载;
前进后退路由管理:由于单页应用在一个页面中显示所有的内容,所以不能使用浏览器的前进后退功能,所有的页面切换需要自己建立堆栈管理;
SEO 难度较大:由于所有的内容都在一个页面中动态替换显示,所以在 SEO 上其有着天然的弱势。
怎样理解 Vue 的单向数据流?
父级 prop 的更新会向下流动到子组件中,但是反过来则不行。
这样会防止从子组件意外改变父级组件的状态,从而导致你的应用的数据流向难以理解。
额外的,每次父级组件发生更新时,子组件中所有的 prop 都将会刷新为最新的值。
这意味着你不应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器的控制台中发出警告。
computed 和 watch 的区别和运用的场景?
computed: 是计算属性,依赖其它属性值,并且 computed 的值有缓存
watch: 更多的是「观察」的作用,类似于某些数据的监听回调
运用场景:
当我们需要进行数值计算,并且依赖于其它数据时,应该使用 computed,因为可以利用 computed 的缓存特性,避免每次获取值时,都要重新计算;
当我们需要在数据变化时执行异步或开销较大的操作时,应该使用 watch,使用 watch 选项允许我们执行异步操作 ( 访问一个 API ),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的。
Vue实例的生命周期:
可以分为4个阶段:初始化阶段、模板编译阶段、挂载阶段、卸载阶段
- new Vue()到created之间的阶段叫作初始化阶段
- 在created钩子函数与beforeMount钩子函数之间的阶段是模板编译阶段。
- beforeMount钩子函数到mounted钩子函数之间是挂载阶段
- 应用调用vm.$destroy方法后,Vue.js的生命周期会进入卸载阶段,Vue.js会将自身从父组件中删除,取消实例上所有依赖的追踪并且移除所有的事件监听器
- 当new Vue()执行后,实例上挂载了 $options属性,初始化事件(on/once/emit),实例属性($parent/$attars/$listeners)和render函数(initLifecycle、initEvents和initRender),然后触发生命周期钩子beforeCreate。
- 随后初始化provide/inject和状态,这里的状态指的是props、methods、data、computed以及watch。接着触发生命周期钩子created。
- 最后,判断用户是否在参数中提供了el选项,如果是,render函数>template模板>el(outer html)
- 如果没有el选项,则停止编译,也就意味着停止了生命周期,直到在该vue实例上调用vm.$mount(el)
- 运行 render 函数(模板)首次被调用生成虚拟dom。触发beforeMount函数。
- 将vnode节点转换为真实dom节点挂载到el上面,数据和DOM都已经被渲染出来了。
- 检测到数据更新时,触发beforeUpdate函数,更新结束后执行,触发updated函数。
- 当要销毁vue实例时,触发beforeDestroy,销毁vue实例,触发destroyed。
keep-alive介绍应用
1.什么是keep-alive
keep-alive是Vue的抽象内置组件,它自身不会渲染一个DOM元素,也不会出现在父组件中,当它包裹动态组件时,在 created钩子函数调用时将需要缓存的 VNode 节点保存在内存中,而不是销毁它们,不会重复渲染DOM,减少加载时间及性能消耗,提高用户体验性
2.参数(Props)
include - 字符串或正则表达式。只有名称匹配的组件会被缓存。
exclude - 字符串或正则表达式。任何名称匹配的组件都不会被缓存。
max - 数字。最多可以缓存多少组件实例
3.对生命周期的变化
activated
在 keep-alive 组件激活时调用
Deactivated
在 keep-alive 组件停用时调用
组件中 data 为什么是一个函数?
如果组件被复用,data 是一个对象,作用域没有隔离,data 属性值会相互影响
如果组件中 data 选项是一个函数,组件每次使用都会return出一个新对象,data 属性值不会互相影响
Vue 组件间通信有哪几种方式?
(1)props / $emit 适用 父子组件通信
(2)ref 与 $parent / $children适用 父子组件通信
(3)EventBus ($emit / $on)适用于 父子、隔代、兄弟组件通信
(4)$attrs/$listeners适用于 隔代组件通信
(5)provide / inject适用于 隔代组件通信
(6)Vuex适用于 父子、隔代、兄弟组件通信
你有对 Vue 项目进行哪些优化?
代码层面的优化:
v-if 和 v-show 区分使用场景
computed 和 watch 区分使用场景
v-for 遍历必须为 item 添加 key,且避免同时使用 v-if
长列表性能优化
事件的销毁
图片资源懒加载
路由懒加载
第三方插件的按需引入
优化无限列表性能
服务端渲染 SSR or 预渲染
Webpack 层面的优化:
Webpack 对图片进行压缩
减少 ES6 转为 ES5 的冗余代码
提取公共代码
模板预编译
提取组件的 CSS
优化 SourceMap
构建结果输出分析
Vue 项目的编译优化
基础的 Web 技术的优化:
开启 gzip 压缩
浏览器缓存
CDN 的使用
使用 Chrome Performance 查找性能瓶颈
响应式原理(变化侦测)
使用发布订阅模式将数据劫持和模板编译结合,实现双向绑定
1、observer: 封装 Object.defineProperty 方法用来劫持对象属性的getter和setter,以此来追踪数据变化。
2、读取数据时触发getter来收集依赖(Watcher)到Dep。
3、修改数据时触发setter,并遍历依赖列表,通知所有相关依赖(Watcher)
4、Dep 类为依赖找一个存储依赖的地方,用来收集和管理依赖,在getter中收集,在setter中通知。
5、Watcher 类就是收集的依赖,实际上是一个订阅器,Watcher会将自己的实例赋值给window.target(全局变量)上,然后去主动访问属性,触发属性的getter,getter中会将此Watcher收集到Dep中,Watcher的update方法会在Dep的通知方法中被调用,触发更新。
6、Observer 类用来将一个对象的所有属性和子属性都变成响应式的,通过递归调用defineReactive来实现。
7、由于无法侦测对象上新增/删除属性,所以提供 $set 和 $delete API5。
Vue 模板编译原理
- 模板字符串 转换成 element AST(解析器)
- 对 AST 进行静态节点标记,主要用来做虚拟DOM的渲染优化(优化器)
- 使用 element AST 生成 render 函数代码字符串(代码生成器)
CommonJS与ES6 Module的区别
- 动态与静态
CommonJS与ES6 Module前者对模块依赖的解决是“动态的”,而后者是“静态的”。在这里“动态”的含义是,模块依赖关系的建立发生在代码运行阶段;而“静态”则表示模块依赖关系的建立发生在代码编译阶段。
require的模块路径可以动态指定,支持传入一个表达式,甚至可以通过if语句判断是否加载某个模块
ES6 Module的导入、导出语句都是声明式的,它不支持将表达式作为导入路径,并且导入、导出语句必须位于模块的顶层作用域
因此我们说,ES6 Module是一种静态的模块结构,它相比CommonJS来说具备以下几点优势:
① 死代码检测和排除。我们可以用静态分析工具检测出哪些模块没有被调用过。
② 模块变量类型检查。JavaScript属于动态类型语言,不会在代码执行前检查类型错误(比如对一个字符串类型的值进行函数调用)。ES6 Module的静态模块结构有助于确保模块之间传递的值或接口类型是正确的。
③ 编译器优化。在CommonJS等动态模块系统中,无论采用哪种方式,本质上导入的都是一个对象,而ES6 Module支持直接导入变量,减少了引用层级,程序效率更高
- 值复制与动态映射
在导入一个模块时,对于CommonJS来说获取的是一份导出值的副本;而在ES6Module中则是值的动态映射,并且这个映射是只读的
- 循环依赖
ES6 Module的特性使其可以更好地支持循环依赖,只是需要由开发者来保证当导入的值被使用时已经设置好正确的导出值
对Webpack的理解:
Webpack 是一个 静态模块打包器,通过分析模块间的依赖关系,在其内部递归构建出一个依赖关系图,其中包含应用程序需要的每个模块,然后将这些模块打包成一个或多个 bundle。最终编绎输出模块为 HTML、JavaScript、CSS 以及各种静态文件(图片、字体等)。
webpack 就像一条生产线,要经过一系列处理流程(loader)后才能将源文件转换成输出结果。 这条生产线上的每个处理流程的职责都是单一的,多个流程之间有存在依赖关系,只有完成当前处理后才能交给下一个流程去处理。
插件就像是一个插入到生产线中的一个功能,在特定的时机对生产线上的资源做处理。 webpack 在运行过程中会广播事件,插件只需要监听它所关心的事件,就能加入到这条生产线中,去改变生产线的运作。
webpack的主要作用如下:
① 模块打包 可以将不同模块的文件打包整合在一起,并且保证它们之间的引用正确,执行有序。
② 编译兼容 ,通过webpack的Loader机制,不仅仅可以帮助我们对代码做polyfill,还可以编译转换诸如.less,.vue,.jsx这类在浏览器无法识别的格式文件,让我们在开发的时候可以使用新特性和新语法做开发,提高开发效率。
③ 能力扩展 通过webpack的Plugin机制,我们在实现模块化打包和编译兼容的基础上,可以进一步实现诸如按需加载,代码压缩等一系列功能
loader的作用:
webpack中的loader是一个函数,主要为了实现源码的转换,所以loader函数会以源码作为参数,比如,将ES6转换为ES5,将less转换为css,然后再将css转换为js,以便能嵌入到html文件中。
常用的loader如下:
- image-loader:加载并且压缩图片文件。
- less-loader:加载并编译 LESS 文件。
- sass-loader:加载并编译 SASS/SCSS 文件。
- css-loader:加载 CSS,支持模块化、压缩、文件导入等特性,使用css-loader必须要配合使用style-loader。
- style-loader:用于将 CSS 编译完成的样式,挂载到页面的 style 标签上。需要注意 loader 执行顺序,style-loader 要放在第一位,loader 都是从后往前执行。
- babel-loader:把 ES6 转换成 ES5
- postcss-loader:扩展 CSS 语法,使用下一代 CSS,可以配合 autoprefixer 插件自动补齐 CSS3 前缀。
- eslint-loader:通过 ESLint 检查 JavaScript 代码。
- vue-loader:加载并编译 Vue 组件。
- file-loader:把文件输出到一个文件夹中,在代码中通过相对 URL 去引用输出的文件 (处理图片和字体)
- url-loader:与 file-loader 类似,区别是用户可以设置一个阈值,大于阈值会交给 file-loader 处理,小于阈值时返回文件 base64 形式编码 (处理图片和字体)。
- source-map-loader:加载额外的 Source Map 文件,以方便断点调试。
plugin的作用:
plugin是一个类,类中有一个apply()方法,主要用于Plugin的安装,可以在其中监听一些来自编译器发出的事件,在合适的时机做一些事情。
webpack中的plugin赋予其各种灵活的功能,例如打包优化、资源管理、环境变量注入等,它们会运行在webpack的不同阶段(钩子 / 生命周期),贯穿了webpack整个编译周期。目的在于「解决 loader 无法实现的其他事情。
常用的plugin如下:
- html-webpack-plugin:可以复制一个有结构的html文件,并自动引入打包输出的所有资源(JS/CSS)
- clean-webpack-plugin:重新打包自动清空 dist 目录
- mini-css-extract-plugin:提取 js 中的 css 成单独文件
- optimize-css-assets-webpack-plugin:压缩css
- uglifyjs-webpack-plugin:压缩js
- commons-chunk-plugin:提取公共代码
- define-plugin:定义环境变量
Webpack中Loader和Plugin的区别:
运行时机
1.loader运行在编译阶段
2.plugins 在整个周期都起作用
使用方式
loader是文件加载器,能够加载资源文件,并对这些文件进行一些处理,诸如编译、压缩等,最终一起打包到指定的文件中;plugin赋予了webpack各种灵活的功能,例如打包优化、资源管理、环境变量注入等,目的是解决 loader无法实现的其他事。
在运行时机上,loader 运行在打包文件之前;plugin则是在整个编译周期都起作用。
在配置上,loader在module.rules中配置,作为模块的解析规则,类型为数组。每一项都是一个 Object,内部包含了 test(类型文件)、loader、options (参数)等属性;plugin在 plugins中单独配置,类型为数组,每一项是一个 plugin 的实例,参数都通过构造函数传入。
性能优化:
js部分:
(1)减少http请求:
web中一次请求要经历发起请求,服务器响应,返回数据,浏览器渲染,才能把数据展示在页面上,其中80%的时间都耗在http请求中,只有20%是浏览器渲染时间。
(2)减少对dom的访问和操作,改为创建文档碎片,进行虚拟dom操作
在浏览器中访问dom节点需要调用查找方法,每个找都是要进行循环遍历才能找到,特别是对于嵌套比较深的dom节点进行查找,过程非常缓慢,于此同时,对dom进行增删改的操作耗费浏览器的性能更大,这也是vue最近比较火,jq越来越少有人用的原因。
(3)dns缓存,浏览器缓存(localstorage、sessionstroage)
dns缓存是当正常访问ip后系统会将这个ip存储起来,当再次访问的时候,系统就会直接把本地的DNS缓存提取显示,等于是加速了网址的解析;浏览器缓存包括页面html缓存和图片, js,css等资源的缓存到用户本地电脑硬盘里,是服务器响应更快和减少网络带宽消耗。
(4)使用异步加载外部js资源和外部资源本地化
对于一些外部资源使用非阻塞异步加载的方法,不阻塞页面加载,比较大的资源下载到本地加载,提高资源加载稳定性。
HTML部分:
(1)标签语义化:
即用合理、正确的标签来展示内容,比如 h1-h6 定义标题,使其易于用户阅读,样式丢失的时候能让页面呈现清晰的结构。
(2)合理规划页面结构:
使用合理的页面结构,使代码更加清晰,有利于后期的维护,图片较小和不重要的转为base64格式,提升加载速度,尽量减少DOM元素的数量与层级。
(3)选择器的合理使用
根据项目大小,选择主要使用class还是id。id选择器优先级最高,访问速度最快。但是在html中每声明一个id,就会在JS底层声明一个全局变量,而全局变量的增多,将会拖慢JS中变量遍历的效率,若变量遍历达到十万次以上,就会出现较显著的延迟,而且容易造成全局变量污染。对于小项目,并无影响,但是对中大型项目来说,尤其是游戏项目,影响很大。个人推荐,当项目较小时,灵活使用class和id,当项目较大时,尽量少使用id。
(4)使用异步加载iframe标签和尽量避免使用table标签
浏览器加载iframe标签时,会阻塞父页面渲染树的构建及HTTP请求,因此尽量使用异步加载iframe;浏览器对table标签的解析是全部生成后再一次性绘制的,因此会造成表格位置较长时间的空白,推荐使用ul及li标签绘制表格。
CSS样式:
(1)禁止使用样式表达式:
CSS表达式从IE5起开始支持,但仅有IE支持。它的解析速度较慢,而且运算次数远比我们想象的要大,随意动动鼠标就能轻松达到上万次运算,会对页面性能造成影响。譬如:"#myDiv{width:expression(document.body.offsetWidth - 110 + "px"); }"。
(2)优化关键选择器:
去掉无效的父级选择器,尽量少在选择器末尾使用通配符,浏览器对CSS选择器的解析式从右往左进行的,减少中间的无效的父级选择器,大大提高效率。
(3)将CSS放在HTML的上面部分
这个策略不能提高网站的加载速度,但它不会让访问者长时间看着空白屏幕或者无格式的文本(FOUT)等待。如果网页大部分可见元素已经加载出来了,访问者才更有可能等待加载整个页面,从而带来对前端的优化效果。这就是知觉性能。
(4)使用CSS雪碧图
将多个图片合并成一个图片(即将多个HTTP请求减少为一个HTTP请求),然后以位置信息定位超链接;
(5)使用 link 而不是@import
加载页面时,link标签引入的CSS被同时加载;@import引入的CSS将在页面加载完毕后被加载,也就是说,@import会组织浏览器的并行下载;link是HTML的元素,不存在兼容性问题;@import只有IE5+才能识别;
(6)减少重排和减少重绘
重排会导致浏览器重新计算整个文档,重新构建渲染树,这一过程会降低浏览器的渲染速度。
我们应该避免发生重排,下面是触发重排的例子
改变 font-size 和 font-family;
改变元素的内外边距;
通过JS改变CSS类;
通过JS获取DOM元素的位置相关属性(如width、height、left等);
CSS伪类激活;
滚动滚动条或者改变窗口大小;
减少重绘,当元素的外观(如color、background、visibility等属性)发生改变时,会触发重绘。在网站的使用过程中,重绘是无法避免的。不过,浏览器对此做了优化,它会将多次的重排、重绘操作合并为一次执行。不过我们仍需要避免不必要的重绘,如页面滚动时触发的hover事件,可以在滚动的时候禁用 hover 事件,这样页面在滚动时会更加流畅;
(7)合并、压缩CSS文件
解决浏览器兼容:
1.css方面:
(1)png的24位图片在ie6会出现图片背景,解决方案是将其转换为png8位图片。
(2)不同的浏览器的html,body的初始值margin和padding是不一样的,其中做法是将其都设置为*{padding:0,margin:0}
(3)如何是要做一个根据不同屏幕进行自适应的页面,最完美的方案是使用媒体查询和rem进行配合使用,下面给出一个解决rem实时响应的js解决方案:
//designWidth:设计稿的实际宽度值,需要根据实际设置
//maxWidth:制作稿的最大宽度值,需要根据实际设置
//这段js的最后面有两个参数记得要设置,一个为设计稿实际宽度,一个为制作稿最大宽度,例如设计稿为750,最大宽度为750,则为(750,750)
;(function(designWidth, maxWidth) {
var doc = document,
win = window,
docEl = doc.documentElement,
remStyle = document.createElement("style"),
tid;
function refreshRem() {
var width = docEl.getBoundingClientRect().width;
maxWidth = maxWidth || 540;
width>maxWidth && (width=maxWidth);
var rem = width * 100 / designWidth;
remStyle.innerHTML = 'html{font-size:' + rem + 'px;}';
}
if (docEl.firstElementChild) {
docEl.firstElementChild.appendChild(remStyle);
} else {
var wrap = doc.createElement("div");
wrap.appendChild(remStyle);
doc.write(wrap.innerHTML);
wrap = null;
}
//要等 wiewport 设置好后才能执行 refreshRem,不然 refreshRem 会执行2次; refreshRem();
win.addEventListener("resize", function() {
clearTimeout(tid); //防止执行两次
tid = setTimeout(refreshRem, 300);
}, false);
win.addEventListener("pageshow", function(e) {
if (e.persisted) { // 浏览器后退的时候重新计算
clearTimeout(tid);
tid = setTimeout(refreshRem, 300);
}
}, false);
if (doc.readyState === "complete") {
doc.body.style.fontSize = "16px";
} else {
doc.addEventListener("DOMContentLoaded", function(e) {
doc.body.style.fontSize = "16px";
}, false);
}
})(750, 750);
(4)ie6双边距的bug,块属性标签float后,又有横行的margin情况下,在ie6显示marign比设置的大,解决方案就是将float标签样式中加入display:inline,把它转换为行内元素,并且这个inline只有ie6可以识别。
(5)在ie下面可以使用常规的方法获取属性,也可以使用getAttribute()获取属性,但是在火狐浏览器中只能使用getAttribute()获取自定义属性,所以解决方法是统一使用getAttribute来获取方法。
(6)在谷歌浏览器中默认小余12号字体的会强制按照12号字体显示,解决方法是加入以下的css样式,或者是使用使用css3的缩放:
-webkit-text-size-adjust:none
(7)超链接访问过后hover样式不在出现了,解决方法是改变css属性:
L-V-H-A:a:link{} a:visited{} a:hover{} a:active{}
(8)在ie6中盒子的width是包括padding,在ie7和ie8中不包括padding。
2.js方面:
(1)触发事件的元素认为目标元素target,在ie中目标元素是包含到serEleelement属性中的
(2)阻止默认行为,ie中,必须将returnValue设置为false,Mozilla中需要调用preventDefault()方法。
(3)阻止默认行为,ie中是使用cancelBubble为true;在Mozilla中药调用stopPropagation()方法。
(4)innerText在IE中能正常工作,但在FireFox中却不行,需要使用textContent来实现,具体代码如下:
if(navigator.appName.indexOf("Explorer") > -1){
document.getElementById('element').innerText = "my text";
} else{
document.getElementById('element').textContent = "my text";
}
3.在html方面:
(1)图片默认是有边距的,解决方案是使用float布局img
(2)边距重叠问题,当两个相邻的元素设置了margin边距,margin会取最大值,舍弃最小值,解决方案是给其中一个元素添加一个父元素,并且设置style:overflow:hidden,代码如下:
<body>
<div id="box1">盒子一</div>
<div style="overflow:hidden"><div id="box2">盒子二</div><div></body>
(3)ul和ol列表缩进问题
消除ul、ol等列表的缩进时,样式应写成:list-style:none;margin:0px;padding:0px;
经验证,在IE中,设置margin:0px可以去除列表的上下左右缩进、空白以及列表编号或圆点,设置padding对样式没有影响;在 Firefox 中,设置margin:0px仅仅可以去除上下的空白,设置padding:0px后仅仅可以去掉左右缩进,还必须设置list- style:none才能去除列表编号或圆点。也就是说,在IE中仅仅设置margin:0px即可达到最终效果,而在Firefox中必须同时设置margin:0px、 padding:0px以及list-style:none三项才能达到最终效果。
super中的props作用是什么?
在class组件中,不管是否写constructor,组件在调用的时候都会自动生成,但是一旦写了constructor,就要写super(),只有写了super才会都自己的this,否则后续使用的时候会报错。
super关键字,它指代父类的实例(即父类的this对象)。子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工。如果不调用super方法,子类就得不到this对象。
React和Vue都是流行的前端框架,各自适合不同的项目类型。具体来说:
React适合大型、高交互、实时数据等复杂的前端项目,例如社交网络、电商网站、金融数据报表、数据分析等。React的核心思想是用组件拼装UI界面,它专注于UI的开发,将应用程序的状态和逻辑分割到组件之外,方便开发和测试。React的高可重用性、高性能和灵活性,使得其非常适合开发大规模的单页面应用程序(SPA)和移动应用程序。
Vue适合于更小型的项目和中小型企业,适合于快速开发应用,快速迭代。Vue的目标是通过尽可能简单的API实现响应式数据绑定和组合视图组件。Vue的核心思想是使用组件化来构建应用程序,并使用单文件组件来将UI组件、CSS和JavaScript代码分离。由于其轻量级的设计和出色的性能,Vue也适合用于打造简单的网站和中等规模的项目,以及适用于移动端、PC端的单页面或多页面应用。
vue 和 react 的区别
(1)语法和模板:Vue 使用类似于 HTML 的模板语法,而 React 使用 JSX 语法,在 JavaScript 中嵌入 HTML 标记。
(2)组件化:Vue 和 React 都采用了组件化的思想,但是 Vue 的组件化更加彻底,一个 Vue 组件包括了 HTML 模板、JavaScript 逻辑和 CSS 样式。React 则将 HTML、JavaScript 和 CSS 分别处理,并且使用 props 和 state 进行数据传递。
(3)响应式更新:Vue 的响应式更新使用了数据劫持的方式,当数据发生变化时,自动更新视图。React 则使用了虚拟 DOM,在数据发生变化时重新渲染整个组件树,然后对比前后两个虚拟 DOM 的差异来更新视图。
(4)生命周期:Vue 和 React 都有生命周期方法,但是具体实现和触发时机不同。Vue 的生命周期方法比较细致,可以在各个阶段进行操作,而 React 只有一些关键的生命周期方法,如 componentDidMount 和 componentDidUpdate。
数据流:Vue 和 React 都采用单向数据流的模式,即只能由父组件向子组件传递数据,不能反过来。但是 Vue 提供了 prop 和事件的方式进行数据传递,而 React 只有 prop 的方式。
现在有一个项目,如何考虑用 vue 还是 react
在考虑使用 Vue 还是 React 时,需要考虑以下几个方面:
(1)项目需求和复杂度:
如果项目需要快速构建,并且组件较为简单、数据流不太复杂,那么可以选择 Vue;如果项目涉及到大规模复杂的组件开发以及高度可定制的数据流管理,那么 React 可能更适合。
(2)团队经验和技能:
如果团队中已有经验丰富的 Vue 或 React 开发人员,那么可以优先选择他们熟悉的框架。否则,可以评估团队技能并选择相应的框架。
(3)社区生态和支持:
Vue 和 React 在社区方面都拥有强大的支持和生态,但 React 更受欢迎,因此可以考虑选择 React 框架,因为它在社区的支持和解决问题方面可能更强大。
(4)性能和体积:
Vue 框架的性能和体积相对较小,因此对于需要快速加载页面和处理大量数据的项目来说,Vue 可能更好一些。而对于需要更灵活的代码组织结构以及更好的代码复用性的项目来说,React 可能更适合。
总之,选择 Vue 还是 React 需要全面考虑项目需求、团队技能、社区支持和性能体积等方面,并根据具体情况权衡利弊,选择最适合项目的框架。
JavaScript为什么是单线程
在JavaScript中,因为主要用途是与用户互动以及操作DOM,同一时间只能做一件事情,这决定了它只能是单线程,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?
所以,为了避免复杂性,从一诞生,JavaScript就是单线程,这已经成了这门语言的核心特征,将来也不会改变。
单线程就意味着,所有任务需要排队,任务可以分成两种,一种是同步任务,另一种是异步任务。同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入”任务队列”的任务,一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",对应的异步任务,进入执行栈,开始执行。
微任务和宏任务都是异步任务,它们都属于一个队列
宏任务:script、setTimeout、setInterval、postMessage、MessageChannel、及Node.js 环境中的setImmediate.
微任务:Promise.then、Object.observe、MutationObserver、及Node.js 环境中的process.nextTick.
CSS文件和JS脚本对DOM解析的影响
(1)CSS文件加载不会阻塞DOM解析,但是会阻塞DOM渲染:
如果CSS不阻塞DOM渲染,那么当DOM解析完后会立即渲染出一个页面样式,当CSS加载完后,页面就会重新渲染成CSS样式,如此就造成用户体验差,渲染次数增加而导致开销大
(2)JS脚本加载会阻塞DOM解析,但浏览器会”偷看“DOM,预先下载相关资源:如果不阻塞,浏览器先解析后面的DOM,当加载完JS后,JS脚本里的内容会删后面的DOM,那么先前的解析就是无用功,所以JS文件会阻塞解析。
但是在实际开发中,我们本不想加载JS脚本的时阻塞DOM解析,因为这会使得用户体验差。那么有没有解决方法呢?
首先是在script标签中加入async属性或者defer属性,也就是异步加载或者延迟加载,这样JS加载和DOM解析就可以同时进行了。
其次现在的浏览器也变得很“聪明”,它会”偷看“,提前下载相关资源,这样也可以解决这个问题。
(3)script标签会触发页面渲染
浏览器遇到script标签且没有defer或async属性时,会触发页面渲染,因为如果前面CSS文件尚未加载完毕,浏览器会等待它加载完成再执行脚本
通过上面的分析,我们可以得出结论,而这些结论也回答了文章开头的那个问题:script标签最好放在底部,link标签最好放在头部。如果头部同时有script标签和link标签,那么最好将script标签放在link标签的上面
结论一:CSS不阻塞DOM解析,但阻塞DOM渲染
结论二:JS阻塞DOM解析,但聪明的浏览器会提前下载相关资源
结论三:浏览器遇到 script标签且没有defer或async属性的 标签时,会触发页面渲染,因而如果前面CSS资源尚未加载完毕时,浏览器会等待它加载完毕在执行脚本
补充:async和defer有什么区别
async异步加载和HTML解析是同时进行,当async加载完后,会阻塞HTML解析,然后执行该文件,执行完成后继续解析HTML。
defer延迟加载和HTML解析也是同时进行,当defer加载完后不会阻塞HTML解析,当HTML解析完成后才开始执行defer加载的文件。
单点登录
(1)同一父域名下的多个子域名,可以使用cookie 的作用域domain做成单点登录:
当domain设置为空时,domain默认为当前域名,并且该域名下的子域名都可以接收到cookie,但是domain参数设置其子域名时,所有域名就接收不到了,包括那个子域名,所以cookie的作用域是: domain本身以及domain下的所有子域名。
结论:同一个父域名下面,a子域名登录后,跳转到b子域名下面,设置cookie的domamin为父域名,b子域名既可以获取到会话状态管理(如用户登录状态、购物车、游戏分数和其它需要记录的信息)
cookie设置语法:
document.cookie = "cookieName=mader; expires=Fri, 31 Dec 2017 15:59:59 GMT; path=/mydir; domain=cnblogs.com; max-age=3600; secure=true";
1.cookieName=mader :name=value,cookie的名称和值
2. expires=Fri, 31 Dec 2017 15:59:59 GMT: expires,cookie过期的日期,如果没有定义,cookie会在对话结束时过期。日期格式为 new Date().toUTCString()
3. path=/mydir: path=path (例如 '/', '/mydir') 如果没有定义,默认为当前文档位置的路径。
4. domain=cnblogs.com: 指定域(例如 'example.com', '.example.com' (包括所有子域名), 'subdomain.example.com') 如果没有定义,默认为当前文档位置的路径的域名部分。
5. max-age=3600: 文档被查看后cookie过期时间,单位为秒
6. secure=true: cookie只会被https传输 ,即加密的https链接传输
(2)不同域下的单点登录
- 用户访问app系统,app系统是需要登录的,但用户现在没有登录。
- 跳转到CAS server,即SSO登录系统,以后图中的CAS Server我们统一叫做SSO系统。 SSO系统也没有登录,弹出用户登录页。
- 用户填写用户名、密码,SSO系统进行认证后,将登录状态写入SSO的session,浏览器(Browser)中写入SSO域下的Cookie。
- SSO系统登录完成后会生成一个ST(Service Ticket),然后跳转到app系统,同时将ST作为参数传递给app系统。
- app系统拿到ST后,从后台向SSO发送请求,验证ST是否有效。
- 验证通过后,app系统将登录状态写入session并设置app域下的Cookie。
- 至此,跨域单点登录就完成了。以后我们再访问app系统时,app就是登录的。接下来,我们再看看访问app2系统时的流程。
- 用户访问app2系统,app2系统没有登录,跳转到SSO。
- 由于SSO已经登录了,不需要重新登录认证。
- SSO生成ST,浏览器跳转到app2系统,并将ST作为参数传递给app2。
- app2拿到ST,后台访问SSO,验证ST是否有效。
- 验证成功后,app2将登录状态写入session,并在app2域下写入Cookie。
- 这样,app2系统不需要走登录流程,就已经是登录了。
前端工程化
前端工程化的主要目标就是解放生产力,提高生产效率,通过一系列的规范,借助工具和框架解决前端开发以及前后端协作的一些问题。前端工程化应该包含从编码开始到发布,运行和维护阶段。
工程化表现:
一切以提高效率、降低成本、质量保障为目的的手段都是工程化。
创建:手脚架
编码:代码的规范/格式化、代码效率
预览:热更新、Mock、Source Map
代码提交:项目整体检查
代码提交:项目整体检查
部署:自动发布
前端工程化的特点:
前端工程化可以分成四个方面来说,分别为模块化、组件化、规范化和自动化。
模块化:
模块化是指将一个文件拆分成多个相互依赖的文件,最后进行统一的打包和加载,这样能够很好的保证高效的多人协作。其中包含
JS 模块化:CommonJS、AMD、CMD 以及 ES6 Module。
CSS 模块化:Sass、Less、Stylus、BEM、CSS Modules 等。其中预处理器和 BEM 都会有的一个问题就是样式覆盖。而 CSS Modules 则是通过 JS 来管理依赖,最大化的结合了 JS 模块化和 CSS 生态,比如 Vue 中的 style scoped。
资源模块化:任何资源都能以模块的形式进行加载,目前大部分项目中的文件、CSS、图片等都能直接通过 JS 做统一的依赖关系处理。
组件化:
不同于模块化,模块化是对文件、对代码和资源拆分,而组件化则是对 UI 层面的拆分。
通常,我们会需要对页面进行拆分,将其拆分成一个一个的零件,然后分别去实现这一个个零件,最后再进行组装。 在我们的实际业务开发中,对于组件的拆分我们需要做不同程度的考量,其中主要包括细粒度和通用性这两块的考虑。 对于业务组件,你更多需要考量的是针对你负责业务线的一个适用度,即你设计的业务组件是否成为你当前业务的 “通用” 组件。
规范化:
正所谓无规矩不成方圆,一些好的规范则能很好的帮助我们对项目进行良好的开发管理。规范化指的是我们在工程开发初期以及开发期间制定的系列规范,其中又包含了
项目目录结构
编码规范:对于编码这块的约束,一般我们都会采用一些强制措施,比如 ESLint、StyleLint 等。
联调规范
文件命名规范
样式管理规范:目前流行的样式管理有 BEM、Sass、Less、Stylus、CSS Modules 等方式。
git flow 工作流:其中包含分支命名规范、代码合并规范等。
定期 code review … 等等
自动化:
从最早先的 grunt、gulp 等,再到目前的 webpack、parcel。这些自动化工具在自动化合并、构建、打包都能为我们节省很多工作。而这些只是前端自动化其中的一部分,前端自动化还包含了持续集成、自动化测试等方方面面。
JavaScript需不需在结尾加分号
第一代码打包压缩的时候不会因为你没加分号而产生莫名其妙的bug;
第二加了分号可以规范自己的代码编写习惯
第三提高代码的可读性