基础
1. 前端需要注意哪些SEO
- 合理的 、 、 :搜索对着三项的权重逐个减小,
title
值强调重点即可,重要关键词出现不要超过2次,而且要靠前,不同页面title
要有所不同;description
把页面内容高度概括,长度合适,不可过分堆砌关键词,不同页面description
有所不同;keywords
列举出重要关键词即可 - 语义化的 HTML 代码,符合W3C规范:语义化代码让搜索引擎容易理解网页
- 重要内容 HTML 代码放在最前:搜索引擎抓取 HTML 顺序是从上到下, 有的搜索引擎对抓取长度有限制,保证重要内容⼀定会被抓取
- 重要内容不要用 js 输出:爬虫不会执行js获取内容
- 少用 iframe :搜索引擎不会抓取 iframe 中的内容
- ⾮装饰性图片必须加 alt
- 提高网站速度: 网站速度是搜索引擎排序的⼀个重要指标
2. 的 和 有什么区别?
- 通常当鼠标滑动到元素上的时候显示
- alt 是
<img>
的特有属性,是图片内容的等价描述,用于图片⽆法加载时显示、读屏器阅读图片 。可提图片高可访问性, 除了纯装饰图片外都必须设置有意义的值, 搜索引擎会重点分析。
3. 如何进行网站性能优化
content 方面
- 减少
Http
请求:合并文件、CSS精灵图、inline Image - 减少
DNS
查询:DNS缓存,将资源分布到恰当数量的主机名 - 减少
DOM
元素数量
- 减少
Server 方面
- 使用CDN
- 配置ETag
- 对组件使用Gzip压缩
Cookie 方面
- 减少 cooike 大小
CSS 方面
- 将样式表放在页面顶部
- 不使用CSS表达式
- 使用
<link>
不使用@import
Javascript 方面
- 将脚本放到页面底部
- 将
JavaScript
和CSS
从外部引入 - 压缩
JavaScript
和CSS
- 删除不需要的脚本
- 减少DOM访问
图片 方面
- 优化图片:根据实际颜色需要选择色深、压缩
- 优化css精灵
- 不要在HTML中拉伸图片
4. HTML5有哪些新特性
HTML5主要新增关于图像、位置、存储、多任务等功能的增加
- 绘画
Canvas
- 用于媒体回访的
video
和audio
元素 - 本地离线存储
localStorage
长期存储数据,浏览器关闭后不会丢失 sessionStorage
的数据在浏览器关闭后自动删除- 语义化更好的内容元素,比如:
article
、footer
、header
、nav
、section
- 表单控件,
calendar
、date
、time
、email
、url
、search
- 新的技术
webworker
、websocket
、geo location
5. 请描述一下,和的区别
cookie
是网站为了标识用户身份而储存在用户本地终端(Client Side)上的数据(通常经过加密)cookie
数据始终在同源的http请求中携带(即使不需要),记会在浏览器和服务器端间来回传递sessionStorage
和localStorage
不会自动把数据发给服务器,仅在本地保存- 存储大小:
cookie
数据大小不能超过4KsessionStorage
和localStorage
虽然也有存储大小的限制,但比cookie
大得多,可以达到5M或者更大
- 有效时间:
localStorage
存储持久数据,浏览器关闭后数据不丢失,除非主动删除数据sessionStorage
数据在当前浏览器窗口关闭后自动删除cookie
设置的cookie
过期时间之前一直有效,即使窗口或者浏览器关闭
6. iframe有哪些缺点
iframe
会阻塞主页面的Onload
事件- 搜索引擎的检索程序无法解读这种页面,不利于
SEO
iframe
和主页面共享连接池,而浏览器对相同域的连接有限制,所以会影响页面的并行加载- 使用
iframe
之前需要考虑这两个确定。如果需要使用iframe
,最好是通过javascript
动态给iframe
添加src
属性值,这样可以绕开以上两个问题。
7. 行内元素、块级元素有哪些?有什么区别?
- 行内元素:
- 行内块级元素:
- 块级元素有:
- 空元素有:
- 行内元素不可以设置宽高,不独占一行,且外边距(margin)和内边距(padding)仅设置左右方向有效而上下无效
- 行内块级元素可以设置宽高,不独占一行,外边距(margin)和内边距(padding)有效
- 块级元素可以设置宽高,独占一行,外边距(margin)和内边距(padding)有效
内联元素则是根据其自身的内容或子元素来决定其宽度
8. HTML全局属性(global attribute)有哪些
class
: 元素设置类标识data-*
:元素增加自定义属性draggable
:设置元素是否可拖拽id
:元素id
,文档内唯一lang
:元素内容的语言style
:行内css
样式title
:元素相关的建议信息
9. Canvas和SVG有什么区别
- svg绘制出来的每一个图像的元素都是独立的
dom
节点,能够方便的绑定事件或用来修改,canvas输出的是一整幅画布 - svg输出的图形是矢量图形,后期可以修改参数来自由放大缩小,不会失真和锯齿,而canvas输出标量画布,就像是一张图片一样,放大会失真或者锯齿。
10. 网站验证码是为了解决什么安全问题
- 区分用户是计算机还是人的公共全自动程序。可以防止恶意破坏密码、刷票、论坛灌水。
- 有效防止黑客对某一个特定注册用户用特定程序暴力破解方式进行不断的登录尝试。
11. viewport
<meta name="viewport" content="width=device-width, initial-scale=1.0">
width
: 设置viewport
宽度,为一个正整数,或字符串'device-width'device-width
: 设备宽度height
: 设置viewport
高度,一般设置了宽度,自动解析出高度,可以不设置initial-scale
默认缩放比例(初始缩放比例),为一个数字,可以带小数minimum-scale
允许用户最小缩放比例,为一个数字,可以带小数maximum-scale
允许用户最大缩放比例,为一个数字,可以带小数user-scalable
是否允许手动缩放<meta name="description" content="不超过150个字符"/> <!--页面描述-->
<meta name="keywords" content="博客,前端,日志"/> <!-- 页面关键词-->
如何处理移动端
1px
被渲染成2px
的问题
局部处理
meta
标签中的viewport
属性,initial-scale
设置为1
rem
按照设计稿标准编写,外加利用CSS
的transform
的scale(0.5)
缩小一倍
全局处理
meta
标签中的viewport
属性,initial-scale
设置为0.5
rem
按照设计稿标准编写
12. 简述一下src和href的区别
- src用于替换当前元素,href用于在当前文档和引用资源之间确认联系
- src是source的缩写,指向外部资源的位置,指向的内容将会嵌入到文档中当前所在位置,在请求src资源时会将其指向的资源下载并应用到文档内,例如js脚本,img图片和frame等元素
<script src="dome.js"></script>
当浏览器解析到该元素时,会暂停其他资源的下载和处理,直到将该资源加载、编译、执行完毕。图片和框架等元素也如此,类似于将所指向资源嵌入当前的标签内。这也是为什么将js脚本放到底部而不是头部href
是Hypertext Reference
的缩写,指向网络资源所在位置,建立和当前元素(锚点)或当前文档(链接)之间的链接,如果我们在文档中添加<link href="common.css" rel="stylesheet" />
那么浏览器会识别该文档为css
文档,就会并行下载资源并且不会停止对当前文档的处理。这也是为什么建议使用link
方式来加载css
,而不是使用@import
13. display: none;、visibility: hidden;和opacity: 0;有什么不同
在Web开发中,display: none;、visibility: hidden;和opacity: 0;都是用来控制元素的显示状态,但它们的行为和效果有所不同;
display: none;
- 行为:完全移除元素,使其在页面布局中不存在。其他元素会填补这个位置,就好像这个元素从未存在过。
- 占用空间:不占用任何空间。
- 事件响应:元素不可见且不会响应用户的任何事件(如点击、鼠标移动等)。
- 重绘和重排:会触发重排(reflow),因为移除了元素会影响其他元素的布局。
visibility: hidden;
- 行为:元素仍然存在于页面布局中,但对用户不可见。
- 占用空间:继续占用空间,其位置和大小保持不变。
- 事件响应:元素不可见且不会响应用户的任何事件(如点击、鼠标移动等)。
- 重绘和重排:只会触发重绘(repaint),不会影响页面布局。
opacity: 0;
- 行为:元素完全透明,对用户不可见,但仍然存在。
- 占用空间:继续占用空间,其位置和大小保持不变。
- 事件响应:元素透明但仍会响应用户的事件(如点击、鼠标移动等)。
- 重绘和重排:只会触发重绘(repaint),不会影响页面布局。
14. 如何创建块级格式化上下文(block formatting context),BFC有什么用?
- 创建规则:
- 根元素
- 浮动元素(float 取值不为 none)
- 绝对定位元素(position取值为absolute 或者 fixed)
display
取值为inline-block
、table-cell
,table-caption
、flex
、inline-flex
,grid
,inline-grid
之一的元素overflow
取值为hidden、auto、scroll
- 作用
- 可以包含浮动元素
- 不被浮动元素覆盖
- 阻止父子元素的
margin
折叠
15. CSS3有哪些新特性?
- 新增各种
css
选择器 - 圆角
border-radius
- 多列布局
- 阴影和反射
- 文字特效
tex-shadow
- 线性渐变
- 旋转
transform
16. 介绍一下标准的CSS的盒子模型
- IE (怪异)模型,W3C盒子模型
- 盒模型:内容(content)、填充(padding)、外边距(margin)、边框(border)
- 区别:IE的content部分把 (border)+ (padding) 也算入
17. ::before 和 :after中双冒号和单冒号 有什么区别?解释⼀下这2个伪元素的作用?
- 单冒号(:)用于CSS3的伪类,双冒号(::)用于CSS3伪元素
- 用于区分伪类和伪元素
18. 如果需要手动写动画,你认为最小时间间隔是多久
- 大多数显示器的默认频率是60Hz,即1秒钟刷新60次,所以理论上最小间隔为(1/60*1000ms)~= 16.7ms
19. 什么是外边距重叠?重叠的结果是什么?
外边距重叠就是
margin-collapse
- 在CSS当中,相邻的两个盒子(可能是兄弟关系也可能是祖先关系)的外边距可以结合成一个单独的外边距。这种合并外边距的方式被称为折叠,并且因而所结合成的外边距称为折叠外边距
折叠结果遵循下列计算规则
- 两个相邻的外边距都是正数时,折叠结果是它们两者之间较大的值。
- 两个相邻的外边距都是负数时,折叠结果是两者绝对值的较大值。
- 两个外边距一正一负时,折叠结果是两者的相加的和。
20. rgba()和opacity的透明效果有什么不同?
rgba(r, g, b, a)
和opacity
都能实现透明效果,但最大的不同是opacity
作用于元素,以及元素内的所有内容的透明度。- 而
rgba(r, g, b, a)
只作用于元素的颜色或其背景色。(设置rgba透明的元素的子元素不会继承透明效果!)
21. css有个content属性,有什么作用?
css的content属性专门应用在before/after伪元素上,用于来插入生成内容。最常见的应用是利用伪类,清除浮动。
.clearfix::after {
content: '.';
display: block;
height: 0;
visibility: hidden;
clear: both
}
.clearfix {
zoom: 1
}
22. 水平居中的方法
- 元素是行内元素,设置父元素
text-align: center
- 如果元素宽度固定,可以设置左右
margin: auto
- 如果元素为绝对定位,设置父元素
position: relative
,元素设:left: 0; right: 0; margin: auto
- 使用
flex-box
布局,指定justify-content: center
display
设置为tabel-ceil
23. 垂直居中的方法
- 将显示方式设置为表格,
display: table-cell
, 同时设置vertial-align: middle
- 使用flex布局,设置为
align-items: center
- 绝对定位中设置
bottom: 0; top: 0
, 并且设置margin: auto
- 绝对定位中固定高度时设置
top: 50%; margin-top: 1/2 * ${height}
- 文本垂直居中设置
line-height: ${height}
24. 常用的垂直水平居中如下
<div class="parent">
<div class="children"></div>
</div>
方式1:父子定位,transform结合translate
// 方式1
.parent {
position: relative;
}
.children {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%)
}
方式2: flex-box布局
// 方式2
.parent {
display: flex;
justify-content: center;
align-items: center;
}
25. 重绘和回流(重排)是什么?如何避免?
- DOM的变化影响到了元素的几何属性(宽高),浏览器重新计算元素的几何属性,其他元素的几何
- 属性和位置也会受到影响,浏览器需要重新构造渲染树,这个过程称为重排,浏览器将受到影响的部分
- 重新绘制到屏幕上的过程称为重绘。
引起重排的原因有:
- 添加或者删除可见的DOM元素
- 元素位置、尺寸、内容改变
- 浏览器页面初始化
- 浏览器窗口尺寸改变,重排一定重绘,重绘不一定重排。
减少重绘和重排的方法:
- 不在布局信息改变时做DOM查询
- 使用
cssText
或者className
一次性改变属性 - 使用
fragment
- 对于多次重排的元素,如动画,使用绝对定位脱离文档流,让它的改变不影响到其他元素
26. 如何实现小于12px的字体效果
- 给元素添加一个可以设置宽高的属性,比如:
display: inline-block
或者display: flex
,且通过css的属性transform: scale(0.5)
来缩放进行实现。
27. 闭包
闭包的关键特性
- 函数的嵌套
- 外部函数的返回值是内部函数
- 内部函数可以访问外部函数中的变量,即使外部函数已经执行完毕。
闭包的特点
- 持久化变量:闭包可以记住并持有其词法环境中的变量,即使外部函数已经执行完毕
- 封装性:闭包可以帮助封装数据,不被外部直接访问
- 词法作用域:闭包能够记住它所在的词法作用域
闭包的用处
- 读取函数内部的变量
- 让函数内部的变量始终保存在内存中
- 好处:能够实现封装和缓存等
- 坏处:消耗内存,不正当使用会造成内存溢出的问题
闭包的注意点
- 由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,也会导致内存泄漏
- 在推出函数之前,将不适用的局部变量清除
- fn = null
// 示例1 计数器
function makeCounter(num) {
let count = 0
return function() {
conut += 1
return count
}
}
28. 作用域链
- 作用域链的作用是保证执行环境里有权访问的变量和函数都是有序的,作用域链的变量只能向上访问,变量访问到
window
对象即可,作用域链向下访问变量是不被允许的 - 简单来说,作用域就是变量与函数的可访问范围,即作用域控制着变量与函数的可见性和生命周期
29. 什么是事件代理
- 事件代理(Event Delegation),又称之为事件委托。是
Javascript
中常用绑定事件的常用技巧。顾名思义,事件代理
即是把原本需要绑定的事件委托给父元素,让父元素担当事件监听的职务。事件代理的原理是DOM元素的事件冒泡,使用事件代理的好处是可以提高性能 - 可以大量节省内存占用,减少事件注册。
- 可以实现当新增子对象时候无需再次对其绑定事件
事件代理的例子
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>事件委托</title>
<style>
* {
margin: 0;
padding: 0;
}
ul {
margin: 20px;
}
li {
list-style: none;
cursor: pointer;
}
li:nth-of-type(2n) {
background-color: skyblue;
}
li:nth-of-type(2n + 1) {
background-color: palegoldenrod;
}
</style>
</head>
<body>
<ul class="ul" id="ul">
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
<li>6</li>
</ul>
<script>
const ulEl = document.getElementById("ul");
let list = document.querySelectorAll("li");
console.log(list, "list");
ulEl.addEventListener("click", (e) => {
const target = e.target;
console.log(target, 'target')
for (let i = 0; i < list.length; i++) {
const el = list[i];
if(el === target) {
console.log(`点击第${i+1}个li,内容是${target.innerHTML}`)
}
}
});
for (let index = 0; index < 5; index++) {
const li = document.createElement('li')
li.innerHTML = list.length + index + 1
ulEl.appendChild(li)
// list = document.querySelectorAll("li");
}
</script>
</body>
</html>
30. Javascript如何实现继承?
1. 原型链继承
传统的继承方式,利用原型链实现对象的继承。
- 优点:
- 简单易理解
- 能够继承父类原型上的属性和方法
- 缺点:
- 子类实例共享父类实例的引用属性,修改一个实例的引用属性会影响到所有实例
- 创建子类实例时,不能向父类构造函数传参
function Parent(name) {
this.name = name
this.colors = ['red', 'blue', 'green']
}
Parent.prototype.sayName = function () {
console.log(this.name)
}
function Child(name, age) {
this.name = name
this.age = age
}
// 设置子类的原型为父类的一个实例
Child.prototype = new Parent()
Child.prototype.constructor = Child
Child.prototype.sayAge = function () {
console.log(this.age)
}
const child = new Child('zhangsan', 18)
child.sayName()
child.sayAge()
2. Object.create
实现继承
Object.create
方法创建一个新对象,并使用现有的对象来提供新创建的对象的原型。
- 优点:
- 简单易用
- 不会有引用属性共享的问题
- 缺点:
- 不能向父类传参
- 继承的属性和方法需要在创建对象时,手动添加。
const parent = {
name: 'zhangsan',
colors: ['red', 'yellow', 'green'],
sayName: function () {
console.log(this.name)
}
}
const child = Object.create(parent)
child.name = 'Child Name'
child.age = 18
child.sayAge = function () {
console.log(this.age)
}
child.sayName()
child.sayAge()
console.log(child.colors)
3. ES6 class
语法
最现代的继承方式,语法也更简洁明了。
- 优点:
- 语法简洁,更符合现代编程习惯
- 可以使用
super
调用父类的构造函数和方法 - 清晰的类结构,易于理解和维护
- 缺点:
- 在某些老旧的Javascript环境中可能不被支持,需要使用Babel等工具进行转译
class Parent {
constructor (name) {
this.name = name
this.colors = ['red', 'yellow', 'green']
}
sayName() {
console.log(this.name)
}
}
class Child extends Parent {
constructor(name, age) {
super(name)
this.age = age
}
sayAge() {
console.log(this.age)
}
}
const child = new Child('zhangsan', 18)
child.sayName()
child.sayAge()
4. 使用组合继承
组合继承结合了原型链继承和借用构造函数(经典继承),避免了各自的缺点。通常被认为是比较好的继承方式。
- 优点:
- 通过调用父类构造函数,避免了引用属性共享问题
- 子类能够向父类传参
- 通过设置子类原型为父类的实例,子类可以继承父类原型上的方法
- 缺点:
- 父类构造函数会被调用两次,导致子类实例上存在多余的父类实例属性
function Parent(name) {
this.name = name
this.colors = ['red', 'blue', 'green']
}
Parent.prototype.sayName = function () {
console.log(this.name)
}
function Child(name, age) {
Parent.call(this, name)
this.age = age
}
// 设置子类的原型为父类的一个实例
Child.prototype = new Parent()
Child.prototype.constructor = Child
Child.prototype.sayAge = function () {
console.log(this.age)
}
const child = new Child('zhangsan', 18)
child.sayName()
child.sayAge()
31. 谈一下Javascript中的This?
this
总是指向函数额直接调用者(而非简洁调用者)- 如果有
new
关键字,this
指向new
出来的那个对象 - 在事件中,
this
指向触发这个事件的对象
this
的值取决于函数的调用方式
- 全局上下文
在全局执行上下文中(即在任何函数之外),
this
引用全局对象。浏览器中,全局对象是window
,而在nodejs中,它是global
console.log(this); // 浏览器中输出:Window {...}
- 函数上下文
在普通函数中,
this
的值依赖于函数的调用方式,也就是调用者。
function foo() {
console.log(this);
}
foo() // 输出:Window {...}
function fooFn() {
console.log('fooFn this : ',this);
}
const obj = { name: 'zhangsan', age: 18 }
fooFn.call(obj) // { name: 'zhangsan', age: 18 }
- 方法调用
当函数作为对象的方法调用时,
this
指向调用该方法的对象。
const obj = {
method: function() {
console.log(this);
}
};
obj.method(); // 输出:obj
- 构造函数调用
当用
new
关键字调用函数时,this
指向新创建的实例对象。
function Person(name) {
this.name = name;
console.log('Person 实例 this: ', this) // Person { name: 'Alice' }
}
const person1 = new Person('Alice');
console.log(person1.name); // 输出:Alice
- 箭头函数
箭头函数没有自己的
this
绑定,它们从定义时的上下文中继承this
值。
const arrowObject = {
name: 'zhangsan',
age: 18,
sayName: function() {
const arrowFunc = () => {
console.log('arrowFunc this:', this);
};
arrowFunc();
}
};
arrowObject.sayName(); // 输出:arrowObject
- 显示绑定
通过
bind
、call
和apply
方法,可以显式地设置this
的值。
function fn() {
console.log('fn this:', this)
}
const person = { name: 'zhangsan', age: 18 }
fn.call(person) // 输出:person
fn.apply(person) // 输出:person
const bindFn = fn.bind(person)
bindFn() // 输出:person
小结
- 全局上下文:this指向全局对象。
- 普通函数:取决于调用方式。
- 方法调用:this指向调用方法的对象。
- 构造函数:this指向新实例。
- 箭头函数:从定义上下文中继承this。
- 显式绑定:通过call、apply、bind设置this。
32. 事件流
事件的发生经历三个阶段:捕获阶段(capturing)、目标阶段(targeting)、冒泡阶段(bubbling)
- 冒泡型事件:点击目标、目标元素触发 ==> 父级元素触发 ==> ... ==> 根节点(一般为document)
- 捕获型事件:点击目标、根节点(一般为document) ==> ... ==> 父级元素触发 ==> 目标元素触发
- 阻止冒泡:使用
e.stopPropagation()
- 阻止捕获:使用
e.preventDefault()
addEventListener
的第三个参数设置true
和false
的区别就是设置事件流的捕获和冒泡类型(默认是false
)true
表示该元素在事件的"捕获阶段"(由根元素document
向着目标元素)响应事件false
表示该元素在事件的"冒泡阶段"(由目标元素向着根元素document
)响应事件
el.addEventListener('click', function(e){}, false)
el.addEventListener('mousemove', function(e){}, false)
el.addEventListener('mouseleave', function(e){})
33. new操作符
在JavaScript中,new
操作符用于创建一个用户定义的对象类型的实例。它的背后执行了几个步骤,这些步骤可以总结为以下几个关键点:
- 创建一个新的空对象。
- 将这个新对象的内部
[[Prototype]]
属性设置为构造函数的prototype
属性。 - 调用构造函数,并将
this
绑定到新创建的对象。 - 如果构造函数返回一个对象,则返回该对象;否则,返回新创建的对象。
让我们逐步剖析 new
操作符的具体工作过程:
示例
考虑一个简单的构造函数:
function Person(name, age) {
this.name = name;
this.age = age;
}
当我们使用 new
操作符创建 Person
的实例时:
const person1 = new Person('Alice', 30);
步骤解析
1. 创建一个新的空对象
JavaScript 引擎会创建一个新的空对象。这个对象是构造函数将要初始化的实例。
const newInstance = {};
2. 将新对象的 [[Prototype]]
属性设置为构造函数的 prototype
属性
新创建的对象的 [[Prototype]]
属性(也称为 __proto__
)会被设置为构造函数的 prototype
属性。
newInstance.__proto__ = Person.prototype;
这一步确保新对象能够继承构造函数原型对象上的方法和属性。
3. 调用构造函数,并将 this
绑定到新创建的对象
构造函数 Person
被调用,且 this
指向 newInstance
。构造函数中的代码运行,并且 this
指向新对象,因此新对象的属性被初始化。
const result = Person.call(newInstance, 'Alice', 30);
构造函数内部执行类似于:
newInstance.name = 'Alice';
newInstance.age = 30;
4. 如果构造函数返回一个对象,则返回该对象;否则,返回新创建的对象
如果构造函数显式返回一个对象(非 null
),则 new
表达式的结果将是该返回对象。否则,new
表达式的结果将是新创建的对象。
if (typeof result === 'object' && result !== null) {
return result;
}
return newInstance;
在本例中,构造函数没有显式返回对象,因此 newInstance
被返回。
完整过程
以下是 new Person('Alice', 30)
的完整过程:
- 创建一个新的空对象
newInstance
。 - 将
newInstance
的__proto__
属性设置为Person.prototype
。 - 调用
Person
构造函数,并将this
绑定到newInstance
,构造函数初始化新对象的属性。 - 返回
newInstance
,因为构造函数没有显式返回其他对象。
代码模拟 new
操作符的行为
以下是一个模拟 new
操作符行为的函数:
function myNew(constructor, ...args) {
// 1. 创建一个新的空对象
const obj = {};
// 2. 将新对象的 `__proto__` 设置为构造函数的 `prototype`
obj.__proto__ = constructor.prototype;
// 3. 调用构造函数,并将 `this` 绑定到新创建的对象
const result = constructor.apply(obj, args);
// 4. 如果构造函数返回一个对象,则返回该对象;否则,返回新创建的对象
return (typeof result === 'object' && result !== null) ? result : obj;
}
// 使用 myNew 来创建一个 Person 的实例
const person2 = myNew(Person, 'Bob', 25);
console.log(person2.name); // 输出:Bob
console.log(person2.age); // 输出:25
这个 myNew
函数演示了 new
操作符的内部工作机制,并且可以像 new
操作符一样创建对象实例。
34. 那些操作会造成内存泄漏
- 内存泄漏指任何对象在您不在拥有或需要它之后仍然存在
setTimeout
的第一个参数使用字符串而非函数的话,会引发内存泄漏- 闭包使用不当
35. 说说AMD和Commonjs的理解
CommonJS
是服务器模块的规范,NodeJS
采用了这个规范,CommonJS
规范加载模块化是同步的,也就是说,只有加载完成,才能执行后面的操作。AMD
规范则是非同步加载模块,允许指定回调函数AMD
推荐的风格通过返回一个对象做为模块对象,CommonJS
的风格通过对module.exports
或exports
的属性赋值来达到暴露模块对象的目的
// CommonJS
// file
exports.fn = () => { console.log('导出成员fn') }
exports.fn1 = () => { console.log('导出成员fn1') }
module.exports.name = 'commonjs'
module.exports = exports
// 导入
const com = require('./common.js')
const { name } = require('./common.js')
// ES modules
export const name = 'app'
export default { name, age: 18 }
// 导入
import esModule, { name } from './esmodule.js'
36. 常见web安全及防护原理
sql
注入原理
就是通过
SQL
命令插入到Web
表单递交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL
命令
- 总的来说有以下几点:
- 永远不要信任用户的输入,要对用户的输入进行校验,可以通过正则表达式,或限制长度,对单引号和双“-”进行转换等
- 永远不用使用动态拼接SQL,可以使用参数化的SQL或者直接使用存储过程进行数据查询存取
- 永远不要使用管理员权限的数据库连接,为每个应用使用单独的权限,有限的数据库连接
- 不要把机密信息明文存放,请加密或者hash掉密码和敏感的信息
XSS原理及防范
- XSS(cross-site scripting)攻击指的是攻击者往Web页面里插入恶意
html
标签或者javascript
代码。比如:攻击者在论坛放一个看似安全的链接,骗取用户点击后,窃取cookie
中的用户私密信息,或者攻击者在论坛中加一个恶意表单,当用户提交表单的时候,却把信息传送到攻击者的服务器中,而不是用户原本以为的信任站点。
XSS防范方法
- 首先代码里对用户输入的地方和变量都需要仔细检查长度和对
<
,>
,;
,'
等字符做过滤或者转义;其次任何内容写到页面之前都必须加以encode
,避免不小心把html tag
弄出来,这一个层面做好,至少可以堵住超过一半的XSS攻击
XSS和CSRF有什么区别?
XSS
是获取信息,不需要提前知道其他用户页面的代码和数据包。CSRF
是代替用户完成指定的动作,需要知道其他用户页面的代码和数据包。要完成一次CSRF
,受害者必须一次完成两个步骤- 登录收信任网站
A
,并在本地生成Cookie
- 在不登出
A
的情况下,访问危险网站B
- 登录收信任网站
CSRF的防御
- 服务端的
CSRF
方式方法很多样,但总的思想都是一致的,就是在客户端页面增加伪随机数 - 通过验证码的方法
37. 同源限制?
- 同源策略指的是:协议,域名,端口相同。同源策略是一种安全协议。
- 举例说明:比如一个黑客程序,利用
Iframe
把真正的银行登录页面嵌到他的页面上,当你使用真实的用户名,密码登录时候,他的页面就可以通过Javascript
读取到你的表单中input
中的内容,这样用户名,密码就轻松到手了。
38. js用哪些方法定义对象
- 对象字面量:
var obj = {}
- 构造函数:
var obj = new Object()
Object.create()
:Object.create(Object.prototype)
39. 同步和异步的区别
- 同步:浏览器访问服务器请求,用户看得到页面刷新,重新发请求,等请求完毕,刷新页面,新内容出现,用户看到新内容,进行下一步操作
- 异步:浏览器访问服务器请求,用户正常操作,浏览器后端进行请求。等请求完毕,页面无需重新刷新,新内容也会出现,用户看到新内容。
40. 渐进增强和优雅降级
- 渐进增强:针对低版本浏览器进行构建页面,保证最基本的功能,然后再针对高级浏览器进行效果交互等改进和追加功能达到更好的用户体验。
- 优雅降级:一开始就构建完整的功能,然后再针对低版本浏览器进行兼容
41. attribute和property的区别是什么?
attribute
是dom
元素在文档中作为html
标签拥有的属性property
就是dom
元素在js
中作为对象拥有的属性- 对于
html
的标准属性来说,attribute
和property
是同步的,是会自动更新的 - 但是对于自定义的属性来说,它们是不同步的
42. 什么是面向对象编程,什么是面向过程编程,它们的异同和优缺点
- 面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。
- 面向对象是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描述某个事物在整个解决问题的步骤中的行为。
- 面向对象是以功能来划分问题,而不是步骤。
43. 面向对象编程思想
- 基本思想是使用对象,类,继承,封装等基本概念来进行程序设计
- 优点:
- 易维护(采用面向对象思想设计的结构,可读性高,由于继承的存在,即使改变需求,那么维护也只是在局部模块,所以维护起来是非常方便和较低成本的。)
- 易扩展
- 开发工作的复用性、继承性高,降低重复工作量
- 缩短了开发周期
44. let 和 var 的区别
let
命令不存在变量提升,如果在let
声明前进行使用,就会导致报错- 如果块区中存在
let
、const
命令,就会形成封闭作用域 - 不允许重复声明,因此,不能在函数内部重新声明参数
var
存在变量提升,在var
声明前使用参数,参数的值为undefined
var
也可以重复声明,后声明的值覆盖之前声明的
45. 箭头函数和普通函数的区别?
- 函数体内的
this
对象,就是定义时所在的对象,而不是使用时所在的对象 - 不可以当作构造函数,也就是说,不可以使用
new
命令,否则就会抛出错误 - 不可以使用
arguments
对象,该对象在函数体内不存在。 - 不可以使用
yield
命令,所以说:箭头函数不能作为Generator
函数
46. 如何渲染几万条数据并不卡主界面
思路:一次渲染一部分是根本 在前端开发中,当需要渲染大量数据时,如何确保页面不卡顿是一个重要的问题。主要思想是避免一次性渲染所有数据,而是分批渲染或按需渲染,从而减少浏览器的渲染负担。以下是几种常见的方法:
1. 分批渲染(Chunking)
- 原理:将大量数据分成多个小批次进行渲染,每批次的数据渲染完后再渲染下一批次。
- 实现:
使用
setTimeout
或requestAnimationFrame
来在每次渲染批次之间给浏览器一些时间处理其他任务。例如:
javascriptfunction renderChunks(data, chunkSize) { let index = 0; function renderNextChunk() { const end = Math.min(index + chunkSize, data.length); for (let i = index; i < end; i++) { // 渲染数据 renderData(data[i]); } index = end; if (index < data.length) { requestAnimationFrame(renderNextChunk); } } requestAnimationFrame(renderNextChunk); }
方式一:虚拟列表(Virtual List)
虚拟列表
- 原理:只渲染可视区域内的数据,其余的数据保持未渲染状态,当用户滚动时动态更新可视区域内的内容。
- 实现:
使用框架(如React的
react-virtualized
或react-window
)或者自己实现。例如使用
react-window
:javascriptimport { FixedSizeList as List } from 'react-window'; const Row = ({ index, style }) => ( <div style={style}> Row {index} </div> ); const Example = ({ data }) => ( <List height={600} itemCount={data.length} itemSize={35} width={300} > {Row} </List> );
方式二:可视化区域加载(Infinite Scroll)
可视化区域加载
- 原理:基于用户的滚动行为,逐步加载数据,当用户滚动到页面底部时,加载更多数据。
- 实现:
监听滚动事件,当滚动到页面底部时,加载更多数据。
例如:
javascriptwindow.addEventListener('scroll', () => { if (window.innerHeight + window.scrollY >= document.body.offsetHeight) { loadMoreData(); } }); function loadMoreData() { // 加载更多数据逻辑 }
方式三:渐进式渲染(Progressive Rendering)
渐进式渲染
- 原理:按优先级渲染内容,优先渲染重要内容,其次逐步渲染次要内容。
- 实现:
可以结合分批渲染,优先渲染用户立即需要看到的数据。
例如:
javascriptfunction renderProgressively(data) { const importantData = data.slice(0, 100); // 先渲染前100条数据 const remainingData = data.slice(100); renderChunks(importantData, 10); // 分批渲染重要数据 setTimeout(() => renderChunks(remainingData, 50), 1000); // 延迟渲染其余数据 }
方式四:按需加载(Lazy Loading)
按需加载
- 原理:仅在需要时才加载和渲染数据,比如图片和视频等资源在进入视口时才加载。
- 实现:
使用
IntersectionObserver
API。例如:
javascriptconst lazyLoad = (target) => { const observer = new IntersectionObserver((entries, observer) => { entries.forEach(entry => { if (entry.isIntersecting) { const img = entry.target; img.src = img.dataset.src; observer.unobserve(img); } }); }); observer.observe(target); }; document.querySelectorAll('img[data-src]').forEach(lazyLoad);
方式五:服务端分页(Server-Side Pagination)
服务端分页
- 原理:将分页逻辑移到服务器端,每次只请求当前页的数据,减少前端一次性加载数据量。
- 实现:
前端通过AJAX请求获取分页数据。
例如:
javascriptlet currentPage = 1; function loadPage(page) { fetch(`/api/data?page=${page}`) .then(response => response.json()) .then(data => { renderData(data); }); } window.addEventListener('scroll', () => { if (window.innerHeight + window.scrollY >= document.body.offsetHeight) { loadPage(++currentPage); } }); function renderData(data) { // 渲染数据逻辑 } // 初始加载第一页 loadPage(currentPage);
通过以上方法,可以有效地解决渲染大量数据时界面卡顿的问题。根据具体应用场景选择合适的策略,确保用户体验流畅。
47. JS如何添加、移除、移动、复制、创建和查找节点
创建新节点
createDocumentFragment()
为创建一个DOM片段createElement('tag')
为创建一个具体的元素createTextNode()
为创建一个文本节点
添加、移除、替换、插入
appendChild()
:添加removeChild()
:移除replaceChild()
:替换insertBefore()
:插入
查找
getElementByTagName()
:通过标签查找getElementsByName()
:通过元素的Name属性的值查找,返回数组getElementById()
:通过元素Id查找getElementsByClassName
:通过元素类名查找,返回数组querySelector
:通过选择器规则查找,返回第一个元素,没有返回null
48. 浏览器缓存
浏览器缓存分为强缓存和协商缓存。当客户端请求某个资源时,获取缓存的流程如下:
- 先根据这个资源的一些
http header
判断它是否命中缓存,如果命中,则直接从本地获取缓存资源,不会发请求到服务器 - 当强缓存没有命中时候,客户端会发送请求到服务器,服务器通过另一些
request header
验证这个资源是否命中协商缓存,称为http
再验证,如果命中,服务器将请求返回,但不返回资源,而是告诉客户端直接从缓存中取,客户端手打返回后就会从缓存中获取资源 - 强缓存和协商缓存的共同之处:
- 减少服务器负载:两者都旨在减少服务器负载和网络流量,通过在用户本地存储资源来减少对服务器的请求。
- 加快页面加载速度:两者都能加快网页加载速度,因为浏览器可以直接从本地缓存中获取资源,而不必每次都从服务器获取。
- 通过HTTP头控制:两者的行为都可以通过HTTP头来控制,例Cache-Control、Expires、ETag和Last-Modified等。
- 不同之处
强缓存的特点是浏览器直接从本地缓存中读取资源,而不会向服务器发送请求验证资源是否更新。
- 控制HTTP头:
- Cache-Control: 包括max-age、public、private、no-store等。例如Cache-Control: max-age=3600表示资源在本地缓存中有效期为3600秒。
- Expires: 这是一个日期和时间,表示资源过期的具体时间点。例如Expires: Wed, 21 Oct 2020 07:28:00 GMT。
- 实现方式:
- 浏览器在本地缓存有效期内,直接使用缓存资源,不与服务器进行任何通信。
- 优缺点:
- 优点:减少请求次数,提高加载速度。
- 缺点:如果资源在缓存期内发生变化,用户可能无法及时获取到更新的资源。
协商缓存的特点是浏览器每次使用缓存资源前,会向服务器发送请求验证资源是否更新。只有在确认资源未更新时,才会使用本地缓存。
- 控制HTTP头:
- ETag: 资源的唯一标识符。例如ETag: "5d8c72a5edda3"。
- Last-Modified: 资源的最后修改时间。例如Last-Modified: Wed, 21 Oct 2020 07:28:00 GMT。
- 实现方式:
- 浏览器发送包含缓存标识符的请求(例如If-None-Match或If-Modified-Since)。
- 服务器根据标识符验证资源是否更新:
- 如果资源未更新,服务器返回304 Not Modified,浏览器使用本地缓存。
- 如果资源已更新,服务器返回新的资源,浏览器更新本地缓存。
- 优缺点:
- 优点:确保用户获取到最新的资源。
- 缺点:每次都需要发送请求进行验证,增加了一定的网络开销。
- 当协商缓存也没命中时,服务器就会将资源发送会客户端
- 当
ctrl+F5
强制刷新网页时,直接从服务器加载,跳过强缓存和协商缓存 - 当
F5
刷新网页时,跳过强缓存,但是会检查协商缓存
49. webSocket
50. Electron
51. 防抖节流
- 防抖
在滚动事件中,需要做个复杂计算或者实现一个按钮的防抖操作,可以通过函数防抖来操作
function debounce(fn, delay = 400) {
let timer = null
return function () {
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
fn.apply(this, arguments)
timer = null
}, delay)
}
}
- 节流
防抖动和节流本质是不一样的。防抖动是将多次执行,只会执行最后一次;而节流就是将多次执行变成每隔一段时间执行。
function throttle(fn, delay) {
let timer = null
return function () {
if (timer) return
timer = setTimeout(() => {
fn.apply(this, arguments)
timer = null
}, delay)
}
}
52. 说说单线程和异步
单线程:只有一个线程,只能做一件事情
- 原因:避免
DOM
渲染的冲突- 浏览器渲染DOM
- JS可以修改DOM结构
- JS执行的时候,浏览器
DOM
渲染会暂停 - 两段JS也不能同时执行,比如:可能存在操作同一
DOM
的情况 - webworker支持多线程,但是不能访问
DOM
- 解决方案:异步
53. 说说事件循环(event loop)
- js是单线程的
- 事件队列
- 先执行js线程的主任务(如果看做宏任务,就可以说是先宏后微)
- 再执行微任务队列(常见:promise)
- 再执行其他宏任务(如:setTimeout,setInterval)
- 循环执行,直到所有事件都执行完毕
54. webpack
55. bebal
Babel 是一个广泛使用的 JavaScript 编译器,主要用于将现代 JavaScript 代码(包括最新的 ECMAScript 版本)转换为向后兼容的版本,以便在不支持新特性的浏览器或环境中运行。Babel 的工作原理可以概括为以下几个步骤:
1. 解析(Parsing)
Babel 首先将输入的 JavaScript 代码解析为抽象语法树(Abstract Syntax Tree,AST)。这个过程包括以下两个子步骤:
词法分析(Lexical Analysis):
- 将源代码拆分为一系列的标记(tokens),这些标记是语法元素的最小单位,例如关键字、变量名、运算符等。
语法分析(Syntactic Analysis):
- 通过解析标记生成 AST。AST 是一种树状数据结构,每个节点代表代码中的一个语法结构。
2. 转换(Transformation)
在生成 AST 后,Babel 会对 AST 进行各种转换。这些转换主要由插件完成。Babel 插件可以添加、修改或删除 AST 中的节点,以实现特定的转换需求。常见的转换包括:
- 将新的 JavaScript 语法(例如箭头函数、类、模板字符串)转换为较旧的等价语法。
- 添加 polyfill 以支持新的内置对象和方法。
- 优化代码,例如去除未使用的变量或简化表达式。
3. 生成(Generation)
在完成 AST 的转换后,Babel 将修改后的 AST 转换回 JavaScript 代码。这一步包括:
- 代码生成(Code Generation):
- 将 AST 转换为源代码字符串。
- 同时生成源映射(source maps),以便在调试时能够将转换后的代码映射回原始代码。
具体示例
假设有以下现代 JavaScript 代码:
const greet = () => {
console.log("Hello, world!");
};
Babel 的工作流程如下:
- 解析:将代码解析为 AST,AST 可能表示为:
Program
├── VariableDeclaration (const)
│ ├── VariableDeclarator
│ │ ├── Identifier (greet)
│ │ └── ArrowFunctionExpression
│ │ └── BlockStatement
│ │ └── ExpressionStatement
│ │ └── CallExpression
│ │ ├── MemberExpression
│ │ │ ├── Identifier (console)
│ │ │ └── Identifier (log)
│ │ └── Literal (Hello, world!)
- 转换:应用插件将箭头函数转换为普通函数表达式,得到更新的 AST:
Program
├── VariableDeclaration (var)
│ ├── VariableDeclarator
│ │ ├── Identifier (greet)
│ │ └── FunctionExpression
│ │ └── BlockStatement
│ │ └── ExpressionStatement
│ │ └── CallExpression
│ │ ├── MemberExpression
│ │ │ ├── Identifier (console)
│ │ │ └── Identifier (log)
│ │ └── Literal (Hello, world!)
- 生成:将更新后的 AST 生成等效的旧版 JavaScript 代码:
var greet = function() {
console.log("Hello, world!");
};
总结
Babel 的核心原理是通过解析、转换和生成三个主要步骤将现代 JavaScript 代码转换为向后兼容的代码。它依赖于插件系统来处理具体的转换任务,确保代码在各种环境中的兼容性和功能性。
56. 前后端路由差别
- 后端每次路由请求都是重新访问服务器
- 前端路由实际上只是js根据URL来操作DOM元素,根据每个页面需要的去服务端请求数据,返回数据后和模板进行组合
57. 设计模式的六大原则
设计模式的六大原则(SOLID 原则)是软件设计中指导良好实践的五个重要原则,它们旨在提高代码的可维护性、可扩展性和可读性。以下是六大原则的详细介绍:
1. 单一职责原则(Single Responsibility Principle, SRP)
定义:一个类应该只有一个引起它变化的原因。即每个类应该只有一个职责(或功能)。
意义:
- 降低类的复杂度,使类更易于理解和修改。
- 提高系统的可维护性,职责单一的类更易于复用和测试。
2. 开放封闭原则(Open/Closed Principle, OCP)
定义:软件实体(类、模块、函数等)应该对扩展开放,对修改封闭。即可以在不修改现有代码的情况下进行功能扩展。
意义:
- 提高系统的稳定性和灵活性,避免修改已有代码引入新的错误。
- 通过继承和多态机制,实现系统的可扩展性。
3. 里氏替换原则(Liskov Substitution Principle, LSP)
定义:子类对象必须能够替换父类对象而不影响系统的正确性。即在使用基类的地方一定能透明地使用其子类对象。
意义:
- 保证继承关系的正确性,避免引入错误的继承关系。
- 提高代码的复用性和可扩展性。
4. 接口隔离原则(Interface Segregation Principle, ISP)
定义:客户端不应该被强迫依赖它不使用的方法。即使用多个专门的接口,而不使用单一的总接口。
意义:
- 避免臃肿的接口,提高系统的灵活性和可维护性。
- 提高系统的解耦性,使接口更易于理解和实现。
5. 依赖倒置原则(Dependency Inversion Principle, DIP)
定义:高层模块不应该依赖低层模块,二者都应该依赖其抽象。抽象不应该依赖细节,细节应该依赖抽象。
意义:
- 降低模块之间的耦合度,提高系统的稳定性和可扩展性。
- 通过依赖抽象而非具体实现,实现模块的独立性和可替换性。
6. 迪米特法则(Law of Demeter, LoD)
定义:一个对象应该对其他对象有尽可能少的了解。即一个对象只与它的直接朋友通信,不应该直接调用其他对象的方法。
意义:
- 降低类之间的耦合度,提高系统的模块化程度。
- 降低系统的复杂性,使代码更易于理解和维护。
总结
这六大原则是面向对象设计的重要指导原则,帮助开发人员设计出更加健壮、灵活和可维护的系统。通过遵循这些原则,可以有效地控制系统的复杂性,增强代码的可读性和可扩展性,从而提升软件的质量和开发效率。
58. 前端代码如何编写
- 高复用低耦合,文件小,好维护,而且好扩展
- 具有可用性、健壮性、可靠性、宽容性等特点
- 遵循设计模式的六大原则
59. 组件封装
为了复用,提高开发效率和代码质量 低耦合 单一职责 可复用性 可维护性
- 分析布局
- 初步开发
- 化繁为简
- 组件抽象