基础

1.前端需要注意哪些SEO

  • 合理的titledescriptionkeywords:搜索对这三项的权重逐个减小,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 请求数量
    • 利用浏览器缓存,公用依赖包(如vueJqueryui组件等)单独打包/单文件在一起,避免重复请求
    • 减小cookie大小,尽量用localStorage代替
    • CDN托管静态文件
    • 开启 Gzip 压缩

2.iframe有那些缺点

  • iframe会阻塞主页面的Onload事件
  • 搜索引擎的检索程序无法解读这种页面,不利于SEO
  • iframe和主页面共享连接池,而浏览器对相同域的连接有限制,所以会影响页面的并行加载

使用iframe之前需要考虑这两个缺点。如果需要使用iframe,最好是通过javascript动态给iframe添加src属性值,这样可以绕开以上两个问题

3.<img>titlealt有什么区别

  • 通常当鼠标滑动到元素上的时候显示
  • alt<img>特有属性,是图片内容的等价描述,用于图片无法加载时显示、读屏器阅读图片。可提图片高可访问性,除了纯装饰图片外都必须设置有意义的值,搜索引擎会重点分析。

4.从浏览器地址栏输入url到显示页面过程

  • 浏览器根据请求的URL交给DNS域名解析,找到真实IP,向服务器发起请求
  • 服务器交给后台处理完成后返回数据,浏览器接收文件(HTMLJSCSS、图象等)
  • 浏览器对加载到的资源(HTMLJSCSS等)进行语法解析,建立相应的内部数据结构(如HTMLDOM
  • 载入解析到的资源文件,渲染页面,完成

5.网站性能优化

  • 减少HTTP请求:合并文件、CSS雪碧图(CSS Sprites
  • 减少DNS查询:DNS缓存、将资源分布到恰当数量的主机名
  • 使用CDN
  • 对组件使用Gzip压缩
  • 将样式表放到页面顶部,使用<link>不使用@import
  • JavaScript脚本放到页面底部,将javascriptcss从外部引入,压缩javascriptcss
  • 图片服务器

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请求步骤

  1. 创建XMLHTTPRequest对象
  2. 使用open方法设置和服务器的交互信息
  3. 设置发送的数据,开始和服务器端交互
  4. 注册事件
  5. 更新界面

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() 进行的是浅拷贝,拷贝的是对象的属性的引用,而不是对象本身。当数据只有一层的时候,是深拷贝
相同的还有数组方法sliceconcat,他们都为浅拷贝,当数据只有一层的时候,可实现深拷贝的效果

  • 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.通过jQueryextend方法实现深拷贝
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异步的方法

  1. 回调函数(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
})

缺点:回调地狱

  1. Promise(ES6)
  2. 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,该promisereosolve值就是函数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请求中携带(即使不需要),会在浏览器和服务器间来回传递
  • sessionStoragelocalStorage不会自动把数据发给服务器,仅在本地保存

存储大小:

  • cookie数据大小不能超过4k
  • sessionStoragelocalStorage虽然也有存储大小的限制,但比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 实际上是一个套了 ChromenodeJS程序

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>
  1. 主页面要是想要调取子页面的showalert方法
myIframe.window.showalert();
  1. 子页面要掉主页面的fullscreen方法
window.parent.fullScreens();
  1. jsiframe 子页面获取父页面元素
window.parent.document.getElementById("元素id");
  1. 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)上,利用 CanvasAPI 进行压缩,完成压缩之后再转成 FileBlob) 对象,上传到远程图片服务器;不过有时候我们也需要将一个 base64 字符串压缩之后再变为 base64 字符串传入到远程数据库或者再转成 FileBlob) 对象
思路就是 File + CanvasdrawImage

canvasdrawImage()方法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);
  1. 如何把系统中图片呈现在浏览器中?
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]);
});
  1. 如何把canvas画布转换成img图像
  • canvas.toDataURL()方法
    • mimeType表示canvas导出来的base64图片的类型,默认是png格式,也即是默认值是'image/png',我们也可以指定为jpg格式'image/jpeg'或者webp等格式。file对象中的file.type就是文件的mimeType类型,在转换时候正好可以直接拿来用(如果有file对象)。
    • qualityArgument表示导出的图片质量,只要导出为jpgwebp格式的时候此参数才有效果,默认值是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);    
    }
});