基础
1.前端需要注意哪些SEO
- 合理的
title
、description
、keywords
:搜索对这三项的权重逐个减小,title
值强调重点即可,重要关键词出现不要超过2次,而且要靠前,不同页面title
要有所不同;description
把页面内容高度概括,长度合适,不可过分堆砌关键词,不同页面description
有所不同;keywords
列举出重要关键词即可 - 语义化的
HTML
代码,符合W3C规范:语义化代码让搜索引擎容易理解网页 - 重要内容
HTML
代码放在最前:搜索引擎抓取HTML顺序是从上到下,有的搜索引擎对抓取长度有限制,保证重要内容一定会被抓取 - 重要内容不要用
js
输出:爬虫不会执行js获取内容 - 少用
iframe
:搜索引擎不会抓取iframe
中的内容 - 非装饰性图片必须加
alt
(非装饰性图片是指除了那些作为元素背景图的图片) - 提高网站速度:网站速度是搜索引擎排序的一个重要指标
性能优化
- css
- 多个
css
合并,尽量减少HTTP
请求 - 将
css
文件放在页面最上面 - 移除空的
css
规则 - 避免使用
CSS
表达式 - 选择器优化嵌套,尽量避免层级过深
- 充分利用
css
继承属性,减少代码量 - 抽象提取公共样式,减少代码量
- 属性值为0时,不加单位
- 属性值为小于1的小数时,省略小数点前面的0
- 使用
CSS Sprites
将多张图片拼接成一张图片,通过CSS background
属性来访问图片内容
- 多个
- js
- 节流、防抖
- 长列表滚动到可视区域动态加载(大数据渲染)
- 图片懒加载(
data-src
) - 使用闭包时,在函数结尾手动删除不需要的局部变量,尤其在缓存dom节点的情况下
- DOM操作优化
- 批量添加
dom
可先createElement
创建并添加节点,最后一次性加入dom
- 批量绑定事件,使用事件委托绑定父节点实现,利用了事件冒泡的特性
- 如果可以使用
innerHTML
代替appendChild
- 在
DOM
操作时添加样式时尽量增加class
属性,而不是通过style
操作样式,以减少重排(Reflow
)
- 批量添加
- 网络
- 减少
HTTP
请求数量 - 利用浏览器缓存,公用依赖包(如
vue
、Jquery
、ui
组件等)单独打包/单文件在一起,避免重复请求 - 减小
cookie
大小,尽量用localStorage
代替 CDN
托管静态文件- 开启
Gzip
压缩
- 减少
2.iframe有那些缺点
iframe
会阻塞主页面的Onload
事件- 搜索引擎的检索程序无法解读这种页面,不利于
SEO
iframe
和主页面共享连接池,而浏览器对相同域的连接有限制,所以会影响页面的并行加载
使用
iframe
之前需要考虑这两个缺点。如果需要使用iframe
,最好是通过javascript
动态给iframe
添加src
属性值,这样可以绕开以上两个问题
3.<img>
的title
和alt
有什么区别
- 通常当鼠标滑动到元素上的时候显示
alt
是<img>
的特有属性
,是图片内容的等价描述,用于图片无法加载时显示、读屏器阅读图片。可提图片高可访问性,除了纯装饰图片外都必须设置有意义的值,搜索引擎会重点分析。
4.从浏览器地址栏输入url到显示页面过程
- 浏览器根据请求的
URL
交给DNS
域名解析,找到真实IP
,向服务器发起请求 - 服务器交给后台处理完成后返回数据,浏览器接收文件(
HTML
、JS
、CSS
、图象等) - 浏览器对加载到的资源(
HTML
、JS
、CSS
等)进行语法解析,建立相应的内部数据结构(如HTML
的DOM
) - 载入解析到的资源文件,渲染页面,完成
5.网站性能优化
- 减少
HTTP
请求:合并文件、CSS雪碧图(CSS Sprites
) - 减少
DNS
查询:DNS
缓存、将资源分布到恰当数量的主机名 - 使用
CDN
- 对组件使用
Gzip
压缩 - 将样式表放到页面顶部,使用
<link>
不使用@import - 将
JavaScript
脚本放到页面底部,将javascript
和css
从外部引入,压缩javascript
和css
- 图片服务器
6.判断变量类型
typeof()
用来判断简单的数据- 判断变量是对象还是数组用
instanceof
(可以正确判断对象的类型),constructor
或者object.prototupe.toString.call()
let a=[1],b={name:'xw'};
a instanceof Array //true
a instanceof Object //true
b instanceof Array //false
b instanceof Object //true
a.constructor==Array //true
b.constructor==Object //true
Object.prototype.toString.call(a)==="[object Array]" //true
Object.prototype.toString.call(b)==="[object Object]" //true
Object.prototype.toString.call(1) //[object Number] "1" [object String]
2.toString(); // 出错:SyntaxError 有很多变通方法可以让数字的字面值看起来像对象。
2..toString(); // 第二个点号可以正常解析
2 .toString(); // 注意点号前面的空格
(2).toString(); // 2先被计算
7.link与@import的区别
- link是HTML方式, @import是CSS方式
- link最大限度支持并行下载,@import过多嵌套导致串行下载,出现FOUC(文档样式短暂失效),页面被加载的时候,link会同时被加载,而@import引用的CSS会等到页面被加载完再加载
- link可以通过rel="alternate stylesheet"指定候选样式
- 浏览器对link支持早于@import,可以使用@import对老浏览器隐藏样式
- @import必须在样式规则之前,可以在css文件中引用其他文件
- import是CSS2.1 提出的,只在IE5以上才能被识别,而link是XHTML标签,无兼容问题
- link支持使用js控制DOM去改变样式,而@import不支持
8.原生AJAX请求步骤
- 创建XMLHTTPRequest对象
- 使用open方法设置和服务器的交互信息
- 设置发送的数据,开始和服务器端交互
- 注册事件
- 更新界面
get请求:
// 第一步:创建异步对象
let xhr = new XMLHttpRequest()
// 第二步:设置请求的url参数,参数1是请求的类型,参数2是请求的url,可以携带参数
xhr.open('get', '/baidu.com?username=1')
// 第三步:设置发送的数据,开始和服务端交互
xhr.send()
// 第四步:注册事件onreadystatechange,当状态改变时会调用
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
// 第五步:如果到达这一步,说明数据返回,请求的页面是存在的
console.log(xhr.responseText)
}
}
post请求:
// 第一步:创建异步对象
let xhr = new XMLHttpRequest()
// post请求一定要添加请求头,不然会报错
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded')
// 第二步:设置请求的url参数,参数1是请求的类型,参数2是请求的url,可以携带参数
xhr.open('post', '/baidu.com')
// 第三步:设置发送的数据,开始和服务端交互
xhr.send('username=1&password=123')
// 第四步:注册事件onreadystatechange,当状态改变时会调用
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
// 第五步:如果到达这一步,说明数据返回,请求的页面是存在的
console.log(xhr.responseText)
}
}
9.深拷贝和浅拷贝
浅拷贝:
浅拷贝是创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以如果其中一个对象改变了这个地址,就会影响到另一个对象
- 1.直接用=赋值
let obj1 = {a: 1}
let obj2 = obj1
- 2.Object.assign
let obj1 = {a: 1}
let obj2 = {}
Object.assign(obj2, obj1)
Object.assign()
进行的是浅拷贝,拷贝的是对象的属性的引用,而不是对象本身。当数据只有一层的时候,是深拷贝
相同的还有数组方法slice
、concat
,他们都为浅拷贝,当数据只有一层的时候,可实现深拷贝的效果
- 3.for in循环只遍历第一层
function shallowObj(obj) {
let result = {}
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
result[key] = obj[key]
}
}
return result
}
let obj1 = {
a: 1,
b: {
c: 2
}
}
let obj2 = shallowObj(obj1)
obj1.b.c = 3
console.log(obj2.b.c) // 3
深拷贝:
深拷贝是将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象
- 1.用
JSON.stringify
把对象转换成字符串,再用JSON.parse
把字符串转换成新的对象
let obj1 = {
a: 1,
b: 2,
}
let obj2 = JSON.parse(JSON.stringify(obj1))
- 2.采用递归去拷贝所有层级属性
function deepClone(obj) {
// 如果传入的值不是一个对象,就不执行
if (Object.prototype.toString.call(obj) !== '[object Object]') return
// 根据传入值的类型初始化返回结果
let result = obj instanceof Array ? [] : {}
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
// 如果obj是个对象,就递归调用deepClone去遍历obj的每一层属性,如果不是对象就直接返回值
result[key] = Object.prototype.toString.call(obj[key]) === '[object Object]' ? deepClone(obj[key]) : obj[key]
}
}
return result
}
// 改进判断对象的方法
console.log(typeof null === 'object') // true
console.log(Object.prototype.toString.call(null) === '[object Object]') // false
- 3.
lodash
函数库实现深拷贝
let obj1 = {
a: 1,
b: 2,
}
let obj2 = _.cloneDeep(obj1)
- 4.通过
jQuery
的extend
方法实现深拷贝
let array = [1,2,3,4]
let newArray = $.extend(true,[],array) // true为深拷贝,false为浅拷贝
- 5.用
slice
实现对数组的深拷贝
let arr1 = ["1","2","3"]
let arr2 = arr1.slice(0)
arr2[1] = "9"
console.log(arr2) // ['1', '9', '3']
console.log(arr1) // ['1', '2', '3']
- 6.使用扩展运算符实现深拷贝
let obj1 = {brand: "BMW", price: "380000", length: "5米"}
let obj2 = { ...obj1, price: "500000" }
10.解决js异步的方法
- 回调函数(
callback
)
//当前有一个ajax调用封装方法
function sendRequest(url, callback) {
let xhr = new XMLHttpRequest()
xhr.open('GET', url)
xhr.onload = callback //callback(xhr.onload)
}
sendRequest('/api/user', (response) => {
//处理response
})
缺点:回调地狱
Promise
(ES6
)async
(ES7
)await
async
是一个异步函数的标识符,返回的值是一个异步的promise
对象。await
等待返回的是一个含有promise
对象的函数。需要注意的是:await
只能在async
函数中
function fn() {
return new Promise((resolve, reject) => {
setTimeout(() => {
let n = parseInt(Math.random() * 6 + 1, 10);
resolve(n);
}, 3000);
});
}
async function test(){ //test是一个异步函数
let n=await fn() //3s后值赋给n
console.log(n)
}
test()
和Promise的区别:
- 函数前面多了一个
aync
关键字。await
关键字只能用在aync
定义的函数内。async
函数会隐式地返回一个promise
,该promise
的reosolve
值就是函数return
的值 - 错误捕获--
try catch
function fn() {
return Promise.reject('error')
}
async function asyncFn() {
try {
await fn()
console.log(1) // 如果上一行出错将直接跳到catch,不会执行到这里
} catch (e) {
console.log(e) // error
}
}
11.请描述一下 cookies,sessionStorage 和 localStorage 的区别
cookie
是网站为了标示用户身份而储存在用户本地终端(Client Side
)上的数据(通常经过加密)cookie
数据始终在同源的http请求中携带(即使不需要),会在浏览器和服务器间来回传递sessionStorage
和localStorage
不会自动把数据发给服务器,仅在本地保存
存储大小:
cookie
数据大小不能超过4k
sessionStorage
和localStorage
虽然也有存储大小的限制,但比cookie
大得多,可以达到5M或更大
有效期:
localStorage
存储持久数据,浏览器关闭后数据不丢失除非主动删除数据sessionStorage
数据在当前浏览器窗口关闭后自动删除cookie
设置的cookie
过期时间之前一直有效,即使窗口或浏览器关闭
对于 cookie,还需要注意安全性:
属性 | 作用 |
---|---|
value | 如果用于保存用户登录态,应该将该值加密,不能使用明文的用户标识 |
http-only | 不能通过 JS 访问 Cookie ,减少 XSS 攻击 |
secure | 只能在协议为 HTTPS 的请求中携带 |
same-site | 规定浏览器不能在跨域请求中携带 Cookie ,减少 CSRF 攻击 |
12.常见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方式方法很多样,但总的思想都是一致的,就是在客户端页面增加伪随机数通过验证码的方法
13.Electron
electron
实际上是一个套了 Chrome
的 nodeJS
程序
14.BOM
- 浏览器对象模型,提供一组对象来完成对浏览器的操作,包括:
window
代表浏览器窗口,同时也是网页中的全局对象navigator
代表当前浏览器信息,通过该对象可识别不同的浏览器location
代表当前浏览器的地址栏信息,可以获取地址栏信息或操作浏览器跳转页面history
代表浏览器的历史记录,可通过该对象操作浏览器历史记录,不能获取具体历史记录,只能操作向前向后翻页且只在当次访问时有效screen
代表用户屏幕信息,屏幕宽高及分辨率等信息
BOM
对象在浏览器中都作为window
对象的属性保存,可通过window
对象调用,也可直接使用navigator
:userAgent
含有用来描述浏览器信息的内容
history
:length
属性获取当次访问的链接数量back()
回退上一个页面forward()
前进下一个页面go()
跳转指定页面,以整数作为参数,整数为向前跳转页面数,负数为向后跳转页面数
location
:- 直接打印
location
可以获取当前页面的完整路径 - 修改
location
为一个绝对或相对路径,则页面会自动跳转并生成相应的历史记录 assign()
跳转至其他页面,与直接修改location的作用一样reload()
重新加载当前页面;传入true
为参数则强制清空缓存刷新页面replace()
使用新的页面替换当前页面,但不生成历史记录
- 直接打印
15.设置函数的默认参数
const increment = (function () {
"use strict";
return function increment(number, value = 1) {
return number + value;
};
})();
console.log(increment(5, 2)); // 返回 7
console.log(increment(5)); // 返回 6
改成箭头函数
const increment = (number, value = 1) => number + value
console.log(increment(5, 2)); // 返回 7
console.log(increment(5)); // 返回 6
16.rest 操作符与函数参数一起使用
function getParams(...args) {
console.log(args) //[1,2,3]
console.log(...args) //1 2 3
}
getParams(1, 2, 3)
17.iframe 跨域通信和不跨域通信
详解
不跨域通信
主页面:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
</head>
<body>
<iframe name="myIframe" id="iframe" class="" src="flexible.html" width="500px" height="500px">
</iframe>
</body>
<script type="text/javascript" charset="utf-8">
function fullscreen() {
alert(1111);
}
</script>
</html>
子页面:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
</head>
<body>
我是子页面
</body>
<script type="text/javascript" charset="utf-8">
// window.parent.fullScreens()
function showalert() {
alert(222);
}
</script>
</html>
- 主页面要是想要调取子页面的
showalert
方法
myIframe.window.showalert();
- 子页面要掉主页面的
fullscreen
方法
window.parent.fullScreens();
js
在iframe
子页面获取父页面元素
window.parent.document.getElementById("元素id");
js
在父页面获取iframe
子页面元素代码如下
window.frames["iframe_ID"].document.getElementById("元素id");
跨域通信
使用postMessage,
子页面
window.parent.postMessage("hello", "http://127.0.0.1:8089");
父页面接收
window.addEventListener("message", function(event) {
alert(123);
});
18.ios端时间显示为NaN
ios上的时间dataString必须满足iso8601或者Rfc2822格式不然无法解析
iso8601:YYYY-MM-DDThh:mm:ss+00:00
而后端返回的可能数据不符合以上格式
ios不支持YYYY-MM-DD格式
"2019-01-01 00:00:00".replace(/\-/g, '/') //正则表达式替换方法
19.JavaScript 实现对上传图片的压缩
读取用户上传的
File
对象,读写到画布(canvas
)上,利用Canvas
的API
进行压缩,完成压缩之后再转成File
(Blob
) 对象,上传到远程图片服务器;不过有时候我们也需要将一个base64
字符串压缩之后再变为base64
字符串传入到远程数据库或者再转成File
(Blob
) 对象
思路就是File
+Canvas
的drawImage
canvas
的drawImage()
方法API
如下:
context.drawImage(img, dx, dy);
context.drawImage(img, dx, dy, dWidth, dHeight);
context.drawImage(img, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
对于本文的图片压缩,需要用的是是5个参数语法。举个例子,一张图片(假设图片对象是img
)的原始尺寸是40003000,现在需要把尺寸限制为400300大小,很简单,原理如下代码示意:
var canvas = document.createElement('canvas');
var context = canvas.getContext('2d');
canvas.width = 400;
canvas.height = 300;
// 核心JS就这个
context.drawImage(img,0,0,400,300);
- 如何把系统中图片呈现在浏览器中?
var reader = new FileReader(), img = new Image();
// 读文件成功的回调
reader.onload = function(e) {
// e.target.result就是图片的base64地址信息
img.src = e.target.result;
};
eleFile.addEventListener('change', function (event) {
reader.readAsDataURL(event.target.files[0]);
});
- 如何把
canvas
画布转换成img
图像
canvas.toDataURL()
方法mimeType
表示canvas
导出来的base64
图片的类型,默认是png
格式,也即是默认值是'image/png'
,我们也可以指定为jpg
格式'image/jpeg'
或者webp
等格式。file
对象中的file.type
就是文件的mimeType
类型,在转换时候正好可以直接拿来用(如果有file对
象)。qualityArgument
表示导出的图片质量,只要导出为jpg
和webp
格式的时候此参数才有效果,默认值是0.92
,是一个比较合理的图片质量输出参数,通常情况下,我们无需再设定。
canvas.toDataURL(mimeType, qualityArgument)
canvas.toBlob()
方法
canvas.toBlob(callback, mimeType, qualityArgument)
// canvas转为blob并上传
canvas.toBlob(function (blob) {
// 图片ajax上传
var xhr = new XMLHttpRequest();
// 开始上传
xhr.open("POST", 'upload.php', true);
xhr.send(blob);
});
demo
HTML代码:
<input id="file" type="file">
JS代码:
var eleFile = document.querySelector('#file');
// 压缩图片需要的一些元素和对象
var reader = new FileReader(), img = new Image();
// 选择的文件对象
var file = null;
// 缩放图片需要的canvas
var canvas = document.createElement('canvas');
var context = canvas.getContext('2d');
// base64地址图片加载完毕后
img.onload = function () {
// 图片原始尺寸
var originWidth = this.width;
var originHeight = this.height;
// 最大尺寸限制
var maxWidth = 400, maxHeight = 400;
// 目标尺寸
var targetWidth = originWidth, targetHeight = originHeight;
// 图片尺寸超过400x400的限制
if (originWidth > maxWidth || originHeight > maxHeight) {
if (originWidth / originHeight > maxWidth / maxHeight) {
// 更宽,按照宽度限定尺寸
targetWidth = maxWidth;
targetHeight = Math.round(maxWidth * (originHeight / originWidth));
} else {
targetHeight = maxHeight;
targetWidth = Math.round(maxHeight * (originWidth / originHeight));
}
}
// canvas对图片进行缩放
canvas.width = targetWidth;
canvas.height = targetHeight;
// 清除画布
context.clearRect(0, 0, targetWidth, targetHeight);
// 图片压缩
context.drawImage(img, 0, 0, targetWidth, targetHeight);
// canvas转为blob并上传
canvas.toBlob(function (blob) {
// 图片ajax上传
var xhr = new XMLHttpRequest();
// 文件上传成功
xhr.onreadystatechange = function() {
if (xhr.status == 200) {
// xhr.responseText就是返回的数据
}
};
// 开始上传
xhr.open("POST", 'upload.php', true);
xhr.send(blob);
}, file.type || 'image/png');
};
// 文件base64化,以便获知图片原始尺寸
reader.onload = function(e) {
img.src = e.target.result;
};
eleFile.addEventListener('change', function (event) {
file = event.target.files[0];
// 选择的文件是图片
if (file.type.indexOf("image") == 0) {
reader.readAsDataURL(file);
}
});
HTML →