irpas技术客

2022年前端面试题整理,持续更新中_zh阿飞_前端面试题

网络投稿 7517

端面试题整理

已同步到掘金、CSDN

掘金地址: https://juejin.cn/post/7075332630417244173

CSDN 地址:https://blog.csdn.net/z1832729975/article/details/123431083

个人整理了很多网上常见的面试题,希望也能通过这来复习 内容有点多,可能 CSDN 上预览效果不好,想要 markdown 文档的可以私信我,推荐使用Typora看

比较好的面试题

2021 年我的前端面试准备 2021 年前端面试必读文章【超三百篇文章/赠复习导图】

CSS、HTML 浏览器内核

IE: trident 内核 Firefox:gecko 内核 Safari: webkit 内核 Opera: 以前是 presto 内核,Opera 现已改用 GoogleChrome 的 Blink 内核 Chrome: Blink(基于 webkit,Google 与 Opera Software 共同开发)

你是怎么理解 HTML 语义化

HTML 语义化简单来说就是用正确的标签来做正确的事。 比如表示段落用 p 标签、表示标题用 h1-h6 标签、表示文章就用 article 等。

DOCTYPE 的作用

Doctype 作用?严格模式与混杂模式如何区分?它们有何差异?

<!DOCTYPE> 声明位于文档中的最前面,处于 <html> 标签之前。告知浏览器以何种模式来渲染文档。

严格模式的排版和 JS 运作模式是 以该浏览器支持的最高标准运行。

在混杂模式中,页面以宽松的向后兼容的方式显示。模拟老式浏览器的行为以防止站 点无法工作。

DOCTYPE 不存在或格式不正确会导致文档以混杂模式呈现。复制代码 你知道多少种 Doctype 文档类型? 该标签可声明三种 DTD 类型,分别表示严格版本、过渡版本以及基于框架的 HTML 文档。 HTML 4.01 规定了三种文档类型:Strict、Transitional 以及 Frameset。 XHTML 1.0 规定了三种 XML 文档类型:Strict、Transitional 以及 Frameset。 Standards (标准)模式(也就是严格呈现模式)用于呈现遵循最新标准的网页,而 Quirks (包容)模式(也就是松散呈现模式或者兼容模式)用于呈现为传统浏览器而设计的网页。

行内元素、块级元素、 空元素有那些? 行内元素 (不能设置宽高,设置宽高无效) a,span,i,em,strong,label行内块元素:img, input块元素: div, p, h1-h6, ul,li,ol,dl,table…知名的空元素 br, hr,img, input,link,meta

可以通过 display 修改 inline-block, block, inline

注意

只有文字才能组成段落,因此 p 标签里面不能放块级元素,特别是 p 标签不能放 div。同理还有这些标签h1,h2,h3,h4,h5,h6,dt ,他们都是文字类块级标签,里面不能放其他块级元素。

meta viewport 是做什么用的,怎么写

使用目的

告诉浏览器,用户在移动端时如何缩放页面

<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale-1, minimum-scale=1" />

with=device-width 将布局视窗(layout viewport)的宽度设置为设备屏幕分辨率的宽度

initial-scale=1 页面初始缩放比例为屏幕分辨率的宽度

maximum-scale=1 指定用户能够放大的最大比例

minimum-scale=1 指定用户能够缩小的最大比例

label 标签的作用

label 标签来定义表单控制间的关系,当用户选择该标签时,浏览器会自动将焦点转到和标签相关的表单控件上。

<label for="Name">Number:</label> <input type="text" name="Name" id="Name" /> <label >Date: <input type="text" name="B" /> </label> canvas 在标签上设置宽高 和在 style 中设置宽高有什么 区别

canvas 标签的 width 和 height 是画布实际宽度和高度,绘制的图形都是在这个上面。 而 style 的 width 和 height 是 canvas 在浏览器中被渲染的高度和宽度。 如果 canvas 的 width 和 height 没指定或值不正确,就被设置成默认值 。

html5 新特性

HTML5 新特性

语义化标签, header, footer, nav, aside,article,section增强型表单视频 video 和音频 audioCanvas 绘图SVG绘图地理定位拖放 APIWebWorkerWebStorage( 本地离线存储 localStorage、sessionStorage )WebSocket css3 新特性

CSS3 有哪些新特性?CSS3 新特性详解

1、圆角效果;2、图形化边界;3、块阴影与文字阴影;4、使用 RGBA 实现透明效果;5、渐变效果;6、使用“@Font-Face”实现定制字体;7、多背景图;8、文字或图像的变形处理;9、多栏布局;10、媒体查询等。

1、颜色:新增RGBA、HSLA模式 2、文字阴影:(text-shadow) 3、边框:圆角(border-radius)边框阴影:box-shadow 4、盒子模型:box-sizing 5、背景:background-size,background-origin background-clip(削弱) 6、渐变:linear-gradient(线性渐变): eg: background-image: linear-gradient(100deg, #237b9f, #f2febd); radial-gradient (径向渐变) 7、过渡:transition可实现动画 8、自定义动画: animate@keyfrom 9、媒体查询:多栏布局@media screen and (width:800px) 10、border-image 11、2D转换:transform:translate(x,y) rotate(x,y)旋转 skew(x,y)倾斜 scale(x,y)缩放 12、3D转换 13、字体图标:Font-Face 14、弹性布局:flex css 选择器

id 选择器( #myid)

类选择器(.myclassname)

标签选择器(div, h1, p)相邻选择器(h1 + p)

子选择器(ul > li)后代选择器(li a)

属性选择器(a[rel = “external”])

伪类选择器(a: hover, li:nth-child)

通配符选择器( * )

!Important > 行内式 > id > 类/伪类/属性 > 标签选择器 > 全局 (对应权重:无穷大∞ > 1000> 100 > 10 > 1 > 0) 盒模型

一个盒子,会有 content,padding,border,margin 四部分,

标准的盒模型的宽高指的是 content 部分

ie 的盒模型的宽高包括了 content+padding+border

我们可以通过 box-sizing 修改盒模型,box-sizing border-box content-box

margin 合并

在垂直方向上的两个盒子,他们的 margin 会发生合并(会取最大的值),比如上边盒子设置margin-bottom:20px,下边盒子设置margin-top:30px;,那么两个盒子间的间距只有30px,不会是50px

解决 margin 合并,我们可以给其中一个盒子套上一个父盒子,给父盒子设置 BFC

margin 塌陷

效果: 一个父盒子中有一个子盒子,我们给子盒子设置margin-top:xxpx结果发现会带着父盒子一起移动(就效果和父盒子设置margin-top:xxpx的效果一样)

解决: 1、给父盒子设置 border,例如设置border:1px solid red; 2、给父盒子设置 BFC

BFC

块级格式化上下文 (block format context)

BFC 的布局规则 *

内部的 Box 会在垂直方向,一个接一个地放置。Box 垂直方向的距离由 margin 决定。属于同一个 BFC 的两个相邻 Box 的 margin 会发生重叠。每个盒子(块盒与行盒)的 margin box 的左边,与包含块 border box 的左边相接触(对于从左往右的格式化,否则相反)。即使存在浮动也是如此。BFC 的区域不会与 float box 重叠。BFC 就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素。反之也如此。计算 BFC 的高度时,浮动元素也参与计算。

触发 BFC 的条件 *

根元素 htmlfloat 的值不是 none。position 的值 absoute、fixeddisplay 的值是 inline-block、table-cell、flex、table-caption 或者 inline-flexoverflow 的值不是 visible

解决什么问题

可以用来解决两栏布局BFC 的区域不会与 float box 重叠

.left { float: flet; } .right { overflow: hidden; }

解决 margin 塌陷和 margin 合并问题

解决浮动元素无法撑起父元素

flex

设为 Flex 布局以后,子元素的 float、clear 和 vertical-align 属性将失效

什么是 rem、px、em 区别

rem 是一个相对单位,rem 的是相对于 html 元素的字体大小,没有继承性

em 是一个相对单位,是相对于父元素字体大小有继承性

px 是一个“绝对单位”,就是 css 中定义的像素,利用 px 设置字体大小及元素的宽高等,比较稳定和精确。

响应式布局

响应式布局有哪些实现方式?什么是响应式设计?响应式设计的基本原理是什么?

1.百分比布局,但是无法对字体,边框等比例缩放

2.弹性盒子布局 display:flex

3.rem 布局,1rem=html 的 font-size 值的大小

css3 媒体查询 @media screen and(max-width: 750px){}

5.vw+vh

6.使用一些框架(bootstrap,vant)

什么是响应式设计:响应式网站设计是一个网站能够兼容多个终端,智能地根据不同设备环境进行相对应的布局

响应式设计的基本原理:基本原理是通过媒体查询检测不同的设备屏幕尺寸设置不同的 css 样式 页面头部必须有 meta 声明的

布局 两栏布局,左边定宽,右边自适应三栏布局、圣杯布局、双飞翼布局 水平垂直居中

方法一:给父元素设置成弹性盒子,子元素横向居中,纵向居中

方法二:父相子绝后,子部分向上移动本身宽度和高度的一半,也可以用 transfrom:translate(-50%,-50%)(最常用方法)

方法三:父相子绝,子元素所有定位为 0,margin 设置 auto 自适应

iframe 有哪些缺点?

iframe 是一种框架,也是一种很常见的网页嵌入方

iframe 的优点:

iframe 能够原封不动的把嵌入的网页展现出来。如果有多个网页引用 iframe,那么你只需要修改 iframe 的内容,就可以实现调用的每一个页面内容的更改,方便快捷。网页如果为了统一风格,头部和版本都是一样的,就可以写成一个页面,用 iframe 来嵌套,可以增加代码的可重用。如果遇到加载缓慢的第三方内容如图标和广告,这些问题可以由 iframe 来解决。

iframe 的缺点:

会产生很多页面,不容易管理。iframe 框架结构有时会让人感到迷惑,如果框架个数多的话,可能会出现上下、左右滚动条,会分散访问者的注意力,用户体验度差。代码复杂,无法被一些搜索引擎索引到,这一点很关键,现在的搜索引擎爬虫还不能很好的处理 iframe 中的内容,所以使用 iframe 会不利于搜索引擎优化。很多的移动设备(PDA 手机)无法完全显示框架,设备兼容性差。iframe 框架页面会增加服务器的 http 请求,对于大型网站是不可取的。现在基本上都是用 Ajax 来代替 iframe,所以 iframe 已经渐渐的退出了前端开发。 link @import 导入 css

link 是 XHTML 标签,除了加载 CSS 外,还可以定义 RSS 等其他事务; @import 属于 CSS 范畴, 只能加载 CSS。 link 引用 CSS 时,在页面载入时同时加载;@import 需要页面网页完全载入以后加载。link 无兼容问题;@import 是在 CSS2.1 提出的,低版本的浏览器不支持。 link 支持使用 Javascript 控制 DOM 去改变样式;而@import 不支持。

DOM 事件机制/模型

DOM0 级模型、IE 事件模型、DOM2 级事件模型

就比如用户触发一个点击事件,有一个触发的过程

事件捕获-阶段(从上大小,从外到内)—>处于目标事件-阶段---->事件冒泡-阶段(从下到上,从内到外)

window.addEventListener( "click", function (event) { event = event || window.event /*ie*/; const target = event.target || event.srcElement; /*ie*/ // 拿到事件目标 // 阻止冒泡 // event.stopPropagation() // event.cancelBubble=true; // ie // 阻止默认事件 // event.preventDefault(); // event.returnValue=false; // ie }, /* 是否使用捕获,默认是fasle, */ fasle );

事件委托

简介:事件委托指的是,不在事件的发生地(直接 dom)上设置监听函数,而是

在其父元素上设置监听函数,通过事件冒泡,父元素可以监听到子元素上事件的

触发,通过判断事件发生元素 DOM 的类型,来做出不同的响应。

举例:最经典的就是 ul 和 li 标签的事件监听,比如我们在添加事件时候,采用

事件委托机制,不会在 li 标签上直接添加,而是在 ul 父元素上添加。

好处:比较合适动态元素的绑定,新添加的子元素也会有监听函数,也可以有事

件触发机制

如果需要手动写动画,你认为最小时间间隔是多久

多数显示器默认频率是 60Hz,即 1 秒刷新 60 次,所以理论上最小间隔为 1/60*1000ms = 16.7ms

::before和:after中双冒号和单冒号有什么区别

单冒号(:)用于 CSS3 伪类,双冒号(::)用于 CSS3 伪元素。 ::before 就是以一个子元素的存在,定义在元素主体内容之前的一个伪元素。并不存在于 dom 之中,只存在在页面之中。 :before 和 :after 这两个伪元素,是在 CSS2.1 里新出现的。起初,伪元素的前缀使用的是单 冒号语法,但随着 Web 的进化,在 CSS3 的规范里,伪元素的语法被修改成使用双冒号,成 为::before ::after

CSS sprites 精灵图

CSS Sprites 其实就是把网页中一些背景图片整合到一张图片文件中,再利用 CSS 的 “background-image”,“ background-repeat ”,“ background-position” 的 组 合 进 行 背 景 定 位 , background-position 可以用数字能精确的定位出背景图片的位置。这样可以减少很多图片请 求的开销,因为请求耗时比较长;请求虽然可以并发,但是也有限制,一般浏览器都是 6 个

重排和重绘

重绘(repaint 或 redraw):当盒子的位置、大小以及其他属性,例如颜色、字 体大小等都确定下来之后,浏览器便把这些原色都按照各自的特性绘制一遍,将 内容呈现在页面上。重绘是指一个元素外观的改变所触发的浏览器行为,浏览器 会根据元素的新属性重新绘制,使元素呈现新的外观。 触发重绘的条件:改变元素外观属性。如:color,background-color 等。 注意:table 及其内部元素可能需要多次计算才能确定好其在渲染树中节点的属性值,比同等元素要多花两倍时间,这就是我们尽量避免使用 table 布局页面的 原因之一。 重排(重构/回流/reflow):当渲染树中的一部分(或全部)因为元素的规模尺寸, 布局,隐藏等改变而需要重新构建, 这就称为回流(reflow)。每个页面至少需要 一次回流,就是在页面第一次加载的时候。 重绘和重排的关系:在回流的时候,浏览器会使渲染树中受到影响的部分失效, 并重新构造这部分渲染树,完成回流后,浏览器会重新绘制受影响的部分到屏幕 中,该过程称为重绘。所以,重排必定会引发重绘,但重绘不一定会引发重排。

JavaScript js 数据类型

8 中, ES6出的 Symbol BigInt

Number String Boolean undefined null Object Symbol BigInt

js 的基本数据类型和复杂数据类型的区别(在堆和栈中,赋值时的不同,一个拷贝值一个拷贝地址)

基本类型和引用类型在内存上存储的区别

null 与 undefined 的异同

相同点:

Undefined 和 Null 都是基本数据类型,这两个基本数据类型分别都只有一个值,就是 undefined 和 null

不同点:

null 转换成数字是 0, undefined 转换数字是NaN

undefined 代表的含义是未定义, null 代表的含义是空对象。

typeof null 返回’object’,typeof undefined 返回’undefined’

null == undefined; // true null === undefined; // false

其实 null 不是对象,虽然 typeof null 会输出 object,但是这只是 JS 存在的一个悠久 Bug。在 JS 的最初版本中使用的是 32 位系统,为了性能考虑使用低位存储变量的类型信息,000 开头代表是对象,然而 null 表示为全零,所以将它错误的判断为 object 。虽然现在的内部类型判断代码已经改变了,但是对于这个 Bug 却是一直流传下来。

说说 JavaScript 中判断数据类型的几种方法

typeof

typeof一般用来判断基本数据类型,除了判断 null 会输出"object",其它都是正确的typeof判断引用数据类型时,除了判断函数会输出"function",其它都是输出"object"

instanceof

Instanceof 可以准确的判断引用数据类型,它的原理是检测构造函数的prototype属性是否在某个实例对象的原型链上, 不能判断基本数据类型

// instanceof 的实现 function instanceofOper(left, right) { const prototype = right.prototype; while (left) { if ((left = left.__proto__) === prototype) { return true; } } return false; } // let obj = {} // Object.getPrototypeOf(obj) === obj.__proto__ ==> true // 实现 instanceof 2 function myInstanceof(left, right) { // 这里先用typeof来判断基础数据类型,如果是,直接返回false if (typeof left !== "object" || left === null) return false; // getProtypeOf是Object对象自带的API,能够拿到参数的原型对象 let proto = Object.getPrototypeOf(left); while (true) { if (proto === null) return false; if (proto === right.prototype) return true; //找到相同原型对象,返回true proto = Object.getPrototypeof(proto); } }

Object.prototype.toString.call() 返回 [object Xxxx] 都能判断

深拷贝和浅拷贝 let obj = { b: "xxx" }; let arr = [{ a: "ss" }, obj, 333]; // 赋值 let arr2 = arr; // 浅拷贝-只拷贝了一层,深层次的引用还是存在 // Object.assign(), ...扩展运算符,slice等 let arr2 = arr.slice(); let arr2 = [...arr]; obj.b = "222"; // arr2[1].b => 222 // arr[2] = 4444 ==> arr2[2] ===> 333 // 深拷贝 // 1. 最简单的,JSON.stringify,但这个有问题,看下面有说明 let arr2 = JSON.parse(JSON.stringify(arr)); // 2. 自己封装,要递归处理

实现深拷贝-简单版

export function deepClone(obj, map = new Map()) { if (!obj && typeof obj !== "object") return obj; if (obj instanceof Date) return new Date(obj); if (obj instanceof RegExp) return new RegExp(obj.source, obj.flags); if (map.get(obj)) { // 如果有循环引用、就返回这个对象 return map.get(obj); } const cloneObj = obj.constructor(); // 数组的就是[],对象就是{} map.set(obj, cloneObj); // 缓存对象,用于循环引用的情况 for (let key in obj) { if (obj.hasOwnProperty(key)) { cloneObj[key] = deepClone(obj[key], map); } } return cloneObj; } JSON.stringify 问题

如果有循环引用就报错

Symbol、function、undefined会丢失

布尔值、数字、字符串的包装对象会转换成原始值

NaN、Infinity 变成 null

Date类型的日期会变成字符串

RegExp、Error被转换成了空对象 {}

模块化

commonjs

// 由nodejs实现 const fs = require("fs"); module.exports = {};

ESM

// 由es6实现 import $ from "jquery"; export default $;

AMD(异步加载模块)

// 由RequireJS实现 define(["juqery", "vue"], function ($, Vue) { // 依赖必须一开始就写好 $("#app"); new Vue({}); });

CMD

// 由SeaJS 实现 define(function (require, exports, module) { var a = require("./a"); a.doSomething(); // .... var b = require("./b"); // 依赖可以就近书写 b.doSomething(); // ... });

UMD (通用加载模块)

(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global = global || self, global.Vue = factory()); }(this, function () { 'use strict'; AMD 和 CMD 的区别有哪些

https://blog.csdn.net/qq_38912819/article/details/80597101

对于依赖的模块,AMD 是提前执行,CMD 是延迟执行。不过 RequireJS 从 2.0 开始,也改成可以延迟执行(根据写法不同,处理方式不同)CMD 推崇依赖就近,AMD 推崇依赖前置 CommonJS 与 ES6 Module 的差异

CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。

CommonJS 模块输出的是值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。ES6 模块的运行机制与 CommonJS 不一样。JS 引擎对脚本静态分析的时候,遇到模块加载命令import,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。换句话说,ES6 的import有点像 Unix 系统的“符号连接”,原始值变了,import加载的值也会跟着变。因此,ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。

CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。

运行时加载: CommonJS 模块就是对象;即在输入时是先加载整个模块,生成一个对象,然后再从这个对象上面读取方法,这种加载称为“运行时加载”。编译时加载: ES6 模块不是对象,而是通过 export 命令显式指定输出的代码,import时采用静态命令的形式。即在import时可以指定加载某个输出值,而不是加载整个模块,这种加载称为“编译时加载”。

CommonJS 加载的是一个对象(即 module.exports 属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。

JS 延迟加载的方式

JavaScript 会阻塞 DOM 的解析,因此也就会阻塞 DOM 的加载。所以有时候我们希望延迟 JS 的加载来提高页面的加载速度。

把 JS 放在页面的最底部script 标签的 defer 属性:脚本会立即下载但延迟到整个页面加载完毕再执行。该属性对于内联脚本无作用 (即没有 「src」 属性的脚本)。Async 是在外部 JS 加载完成后,浏览器空闲时,Load 事件触发前执行,标记为 async 的脚本并不保证按照指定他们的先后顺序执行,该属性对于内联脚本无作用 (即没有 「src」 属性的脚本)。动态创建 script 标签,监听 dom 加载完毕再引入 js 文件 call、apply 、bind

call,apply, bind 都是改变 this 指向,bind 不会立即执行,会返回的是一个绑定 this 的新函数

面试官问:能否模拟实现 JS 的 call 和 apply 方法

obj.call(this指向, 参数1, 参数2)ss obj.apply(this指向, [参数1, 参数2]) function fn(age) { console.log(this, age) } const obj = {name:''} const result = fn.bind(obj) // bind会返回一个新的函数 result(20) // 实现一个 apply Function.prototype.myApply = function (context) { context = context || window; const fn = Symbol(); context[fn] = this; var res = context[fn](...arguments[1]); delete context[fn]; return res; };

实现一个 bind

// 最终版 删除注释 详细注释版请看上文 Function.prototype.bind = Function.prototype.bind || function bind(thisArg) { if (typeof this !== "function") { throw new TypeError(this + " must be a function"); } var self = this; var args = [].slice.call(arguments, 1); var bound = function () { var boundArgs = [].slice.call(arguments); var finalArgs = args.concat(boundArgs); if (this instanceof bound) { if (self.prototype) { function Empty() {} Empty.prototype = self.prototype; bound.prototype = new Empty(); } var result = self.apply(this, finalArgs); var isObject = typeof result === "object" && result !== null; var isFunction = typeof result === "function"; if (isObject || isFunction) { return result; } return this; } else { return self.apply(thisArg, finalArgs); } }; return bound; }; 防抖

debounce 所谓防抖,就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。

function debounce(func, wait, immediate) { let timeout; return function () { const context = this; const args = [...arguments]; if (timeout) clearTimeout(timeout); if (immediate) { const callNow = !timeout; timeout = setTimeout(() => { timeout = null; }, wait); if (callNow) func.apply(context, args); } else { timeout = setTimeout(() => { func.apply(context, args); }, wait); } }; } 节流

就是指连续触发事件但是在 n 秒中只执行一次函数

function throttle(fn, wait) { let pre = 0; return function (...args) { let now = Date.now(); if (now - pre >= wait) { fn.apply(this, args); pre = now; } }; } 闭包

闭包是指有权访问另一个函数作用域中的变量的函数 ——《JavaScript 高级程序设计》

当函数可以记住并访问所在的词法作用域时,就产生了闭包,

即使函数是在当前词法作用域之外执行 ——《你不知道的 JavaScript》

闭包用途: 能够访问函数定义时所在的词法作用域(阻止其被回收)私有化变量模拟块级作用域创建模块 闭包缺点:会导致函数的变量一直保存在内存中,过多的闭包可能会导致内存泄漏 原型、原型链(高频)

原型: 对象中固有的__proto__属性,该属性指向对象的prototype原型属性。

原型链: 当我们访问一个对象的属性时,如果这个对象内部不存在这个属性,那么它就会去它的原型对象里找这个属性,这个原型对象又会有自己的原型,于是就这样一直找下去,也就是原型链的概念。原型链的尽头一般来说都是Object.prototype所以这就是我们新建的对象为什么能够使用toString()等方法的原因。

特点: JavaScript对象是通过引用来传递的,我们创建的每个新对象实体中并没有一份属于自己的原型副本。当我们修改原型时,与之相关的对象也会继承这一改变。

this 指向、new 关键字

this对象是是执行上下文中的一个属性,它指向最后一次调用这个方法的对象,在全局函数中,this等于window,而当函数被作为某个对象调用时,this 等于那个对象。 在实际开发中,this 的指向可以通过四种调用模式来判断。

函数调用,当一个函数不是一个对象的属性时,直接作为函数来调用时,this指向全局对象。方法调用,如果一个函数作为一个对象的方法来调用时,this指向这个对象。构造函数调用,this指向这个用new新创建的对象。第四种是 apply 、 call 和 bind 调用模式,这三个方法都可以显示的指定调用函数的 this 指向。apply接收参数的是数组,call接受参数列表,`` bind方法通过传入一个对象,返回一个this绑定了传入对象的新函数。这个函数的 this指向除了使用new `时会被改变,其他情况下都不会改变。

new

面试官问:能否模拟实现 JS 的 new 操作符

首先创建了一个新的空对象设置原型,将对象的原型设置为函数的prototype对象。让函数的this指向这个对象,执行构造函数的代码(为这个新对象添加属性)判断函数的返回值类型,如果是值类型,返回创建的对象。如果是引用类型,就返回这个引用类型的对象。 // new 操作符的实现 function newOperator(ctor) { if (typeof ctor !== "function") { throw "newOperator function the first param must be a function"; } newOperator.target = ctor; var newObj = Object.create(ctor.prototype); var argsArr = [].slice.call(arguments, 1); var ctorReturnResult = ctor.apply(newObj, argsArr); var isObject = typeof ctorReturnResult === "object" && ctorReturnResult !== null; var isFunction = typeof ctorReturnResult === "function"; if (isObject || isFunction) { return ctorReturnResult; } return newObj; } 作用域、作用域链、变量提升

作用域负责收集和维护由所有声明的标识符(变量)组成的一系列查询,并实施一套非常严格的规则,确定当前执行的代码对这些标识符的访问权限。(全局作用域、函数作用域、块级作用域)。 作用域链就是从当前作用域开始一层一层向上寻找某个变量,直到找到全局作用域还是没找到,就宣布放弃。这种一层一层的关系,就是作用域链。

继承(含 es6)、多种继承方式 function Animal(name) { // 属性 this.name = name || "Animal"; // 实例方法 this.sleep = function () { console.log(this.name + "正在睡觉!"); }; } // 原型方法 Animal.prototype.eat = function (food) { console.log(this.name + "正在吃:" + food); };

(1)第一种是以原型链的方式来实现继承,但是这种实现方式存在的缺点是,在包含有引用类型的数据时,会被所有的实例对象所共享,容易造成修改的混乱。还有就是在创建子类型的时候不能向超类型传递参数。

// 原型链继承 function Cat() {} Cat.prototype = new Animal("小黄"); // 缺点 无法实现多继承 来自原型对象的所有属性被所有实例共享 Cat.prototype.name = "cat";

(2)第二种方式是使用借用构造函数的方式,这种方式是通过在子类型的函数中调用超类型的构造函数来实现的,这一种方法解决了不能向超类型传递参数的缺点,但是它存在的一个问题就是无法实现函数方法的复用,并且超类型原型定义的方法子类型也没有办法访问到。

// 借用构造函数继承 function Cat() { Animal.call(this, "小黄"); // 缺点 只能继承父类实例的属性和方法,不能继承原型上的属性和方法。 }

(3)第三种方式是组合继承,组合继承是将原型链和借用构造函数组合起来使用的一种方式。通过借用构造函数的方式来实现类型的属性的继承,通过将子类型的原型设置为超类型的实例来实现方法的继承。这种方式解决了上面的两种模式单独使用时的问题,但是由于我们是以超类型的实例来作为子类型的原型,所以调用了两次超类的构造函数,造成了子类型的原型中多了很多不必要的属性。

(4)第四种方式是原型式继承,原型式继承的主要思路就是基于已有的对象来创建新的对象,实现的原理是,向函数中传入一个对象,然后返回一个以这个对象为原型的对象。这种继承的思路主要不是为了实现创造一种新的类型,只是对某个对象实现一种简单继承,ES5 中定义的 Object.create() 方法就是原型式继承的实现。缺点与原型链方式相同。

function object(o) { function F() {} F.prototype = o; return new F(); }

(5)第五种方式是寄生式继承,寄生式继承的思路是创建一个用于封装继承过程的函数,通过传入一个对象,然后复制一个对象的副本,然后对象进行扩展,最后返回这个对象。这个扩展的过程就可以理解是一种继承。这种继承的优点就是对一个简单对象实现继承,如果这个对象不是我们的自定义类型时。缺点是没有办法实现函数的复用。

function createAnother(original) { var clone = object(original); //通过调用object函数创建一个新对象 clone.sayHi = function () { //以某种方式来增强这个对象 alert("hi"); }; return clone; //返回这个对象 }

(6)第六种方式是寄生式组合继承,组合继承的缺点就是使用超类型的实例做为子类型的原型,导致添加了不必要的原型属性。寄生式组合继承的方式是使用超类型的原型的副本来作为子类型的原型,这样就避免了创建不必要的属性。

function extend(subClass, superClass) { var prototype = object(superClass.prototype); //创建对象 prototype.constructor = subClass; //增强对象 subClass.prototype = prototype; //指定对象 } 类型转换

大家都知道 JS 中在使用运算符号或者对比符时,会自带隐式转换,规则如下:

-、*、/、% :一律转换成数值后计算

+:

数字 + 字符串 = 字符串, 运算顺序是从左到右

数字 + 对象, 优先调用对象的 valueOf -> toString

数字 + boolean/null -> 数字

数字 + undefined -> NaN

[1].toString() === ‘1’ 内部调用 .join 方法

{}.toString() === ‘[object object]’

NaN !== NaN 、+undefined 为 NaN

Object.is()与比较操作符==、===的区别? ==会先进行类型转换再比较===比较时不会进行类型转换,类型不同则直接返回 falseObject.is()在===基础上特别处理了NaN,-0,+0,保证-0 与+0 不相等,但 NaN 与 NaN 相等 ==操作符的强制类型转换规则 字符串和数字之间的相等比较,将字符串转换为数字之后再进行比较。其他类型和布尔类型之间的相等比较,先将布尔值转换为数字后,再应用其他规则进行比较。null 和 undefined 之间的相等比较,结果为真。其他值和它们进行比较都返回假值。对象和非对象之间的相等比较,对象先调用 ToPrimitive 抽象操作后,再进行比较。如果一个操作值为 NaN ,则相等比较返回 false( NaN 本身也不等于 NaN )。如果两个操作值都是对象,则比较它们是不是指向同一个对象。如果两个操作数都指向同一个对象,则相等操作符返回 true,否则,返回 false。

ES6 新增 Symbol 类型 表示独一无二的值,用来定义独一无二的对象属性名;const/let 都是用来声明变量,不可重复声明,具有块级作用域。存在暂时性死区,不存在变量提升。(const 一般用于声明常量);变量的解构赋值(包含数组、对象、字符串、数字及布尔值,函数参数),剩余运算符(…rest);模板字符串(${data});...扩展运算符(数组、对象);;箭头函数;Set 和 Map 数据结构;Proxy/Reflect;Promise;async 函数;Class;Module 语法(import/export)。 let/const

const声明一个只读的常量。一旦声明,常量的值就不能改变 https://es6.ruanyifeng.com/#docs/let

var 在全局作用域中声明的变量会变成全局变量

let、const 和 var 的区别

不允许重复声明

不存在变量提升

// var 的情况 console.log(foo); // 输出undefined var foo = 2; // let 的情况 console.log(bar); // 报错ReferenceError let bar = 2;

暂时性死区(不能在未声明之前使用)

注意暂时性死区和不存在变量提升不是同一个东西

只要块级作用域内存在let命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响。

var tmp = 123; // 声明了 tmp if (true) { tmp = "abc"; // ReferenceError let tmp; }

块级作用域:用 let 和 const 声明的变量,在这个块中会形成块级作用域

es5 只有函数作用域和全局作用域

IIFE 立即执行函数表达式

// IIFE 写法 (function () { var tmp = ...; ... }()); // 块级作用域写法 { let tmp = ...; ... } // 函数声明 function a() {} // 函数表达式 const b = function () {}; ES6 的一些叫法

reset 参数

function add(...values) { let sum = 0; for (var val of values) { sum += val; } return sum; } add(2, 5, 3); // 10

扩展运算符

console.log(...[1, 2, 3]); // 1 2 3 const b = { ...{ a: "2", b: "3" } };

?. 可选链运算符

左侧的对象是否为null或undefined。如果是的,就不再往下运算,而是返回undefined

a?.b; // 等同于 a == null ? undefined : a.b; // 注意 undefined == null ==> true

?? Null 判断运算符

https://es6.ruanyifeng.com/#docs/operator#Null-%E5%88%A4%E6%96%AD%E8%BF%90%E7%AE%97%E7%AC%A6

const headerText = response.settings.headerText ?? "Hello, world!"; const animationDuration = response.settings.animationDuration ?? 300; const showSplashScreen = response.settings.showSplashScreen ?? true;

但左侧的为 undefined或者null是就返回右边的,否则就直接返回左边的

箭头函数和普通函数的区别 箭头函数没有this,this是继承于当前的上下文,不能通过call,apply,bind去改变 this箭头函数没有自己的 arguments 对象,但是可以访问外围函数的 arguments 对象不能通过 new 关键字调用(不能作为构造函数),同样也没有 new.target 和原型 如何解决异步回调地狱

promise、generator、async/await

mouseover 和 mouseenter 的区别

mouseover:当鼠标移入元素或其子元素都会触发事件,所以有一个重复触发,

冒泡的过程。对应的移除事件是 mouseout

mouseenter:当鼠标移除元素本身(不包含元素的子元素)会触发事件,也就是

不会冒泡,对应的移除事件是 mouseleave

setTimeout、setInterval 和 requestAnimationFrame 之间的区别

与 setTimeout 和 setInterval 不同,requestAnimationFrame 不需要设置时间 间隔, 大多数电脑显示器的刷新频率是 60Hz,大概相当于每秒钟重绘 60 次。大多数浏 览器都会对重绘操作加以限制,不超过显示器的重绘频率,因为即使超过那个频 率用户体验也不会有提升。因此,最平滑动画的最佳循环间隔是 1000ms/60,约 等于 16.6ms。 RAF 采用的是系统时间间隔,不会因为前面的任务,不会影响 RAF,但是如果前 面的任务多的话,会响应 setTimeout 和 setInterval 真正运行时的时间间隔。 特点:

(1)requestAnimationFrame 会把每一帧中的所有 DOM 操作集中起来,在一次 重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率。 (2)在隐藏或不可见的元素中,requestAnimationFrame 将不会进行重绘或回 流,这当然就意味着更少的 CPU、GPU 和内存使用量 (3)requestAnimationFrame 是由浏览器专门为动画提供的 API,在运行时浏览 器会自动优化方法的调用,并且如果页面不是激活状态下的话,动画会自动暂停, 有效节省了 CPU 开销。

vue

vue2 是通过Object.defineProperty来实现响应式的,所以就会有一些缺陷

当修改一个对象的某个键值属性时,当这个键值没有在这个对象中,vue 不能做响应式处理但直接修改数组的某一项(arr[index]='xxx')vue 不能做响应式处理

可用下面的解决响应式

Vue.set ==> this.$set(对象\数组, key 值、index, value)修改数组length, 调用数据的 splice 方法 vue 生命周期 beforeCreate 实例化之前这里能拿到this,但是还不能拿到data里面的数据 created 实例化之后 beforeMount() mounted() $el beforeUpdate updated beforeDestroy 清除定时/移除监听事件 destroyed // 被keep-alive 包裹的 // keep-alive 标签 include exclude max activated() {}, deactivated() {}, // 父子 父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted。 // 离开页面:实例销毁 --> DOM卸载 parent beforeDestroy child beforeDestroy child destroyed parent destroyed Vue 的 data 为什么是一个函数

因为 Vue 的组件可能会在很多地方使用, 会产生多个实例,如果返回的是对象的, 这些组件之间的数据是同一份(引用关系),那么修改其中一个组件的数据,另外一个组件的数据都会被修改到

Vue key 值的作用

看这个视频,你能给面试官说这些,你就很不错了,vue 和 react 的差不多 https://·/post/7018805943710253086#heading-63

type-类型别名

interface-接口

接口重名会合并、类型别名重名会报错

interface Person { name: string; } interface Person { age: number; } // 这个接口合并,变成下面的 interface Person { name: string; age: number; } type Aanimal = { name: string }; type Aanimal = { age: number }; // 会报错、重名了

两者都可以用来描述对象或函数的类型,但是语法不同

interface

interface Point { x: number; y: number; } interface SetPoint { (x: number, y: number): void; }

type

type Point = { x: number; y: number; }; type SetPoint = (x: number, y: number) => void;

类型别名可以为任何类型引入名称。例如基本类型,联合类型等

// primitive type Name = string; // object type PartialPointX = { x: number }; type PartialPointY = { y: number }; // union type PartialPoint = PartialPointX | PartialPointY; // tuple type Data = [number, string]; // dom let div = document.createElement("div"); type B = typeof div;

扩展

两者的扩展方式不同,但并不互斥。接口可以扩展类型别名,同理,类型别名也可以扩展接口。

接口的扩展就是继承,通过 extends 来实现。类型别名的扩展就是交叉类型,通过 & 来实现。

接口扩展接口

interface PointX { x: number; } interface Point extends PointX { y: number; }

类型别名扩展类型别名

type PointX = { x: number; }; type Point = PointX & { y: number; };

接口扩展类型别名

type PointX = { x: number; }; interface Point extends PointX { y: number; }

类型别名扩展接口

interface PointX { x: number; } type Point = PointX & { y: number; }; keyof 和 typeof 关键字的作用?

keyof 索引类型查询操作符 获取索引类型的属性名,构成联合类型。 typeof 获取一个变量或对象的类型。

unknown, any 的区别

unknown 类型和 any 类型类似。与 any 类型不同的是。unknown 类型可以接受任意类型赋值,但是 unknown 类型赋值给其他类型前,必须被断言


1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,会注明原创字样,如未注明都非原创,如有侵权请联系删除!;3.作者投稿可能会经我们编辑修改或补充;4.本站不提供任何储存功能只提供收集或者投稿人的网盘链接。

标签: #前端面试题 #适合初中级的前端开发