文章目录
- javascript 汇总
- 数组
- 遍历对象
- 作用域
- 作用域链
- 原型、原型链、继承
- JS 垃圾回收机制
- 函数表达式(匿名函数)
- this
- New 构造函数
- 深拷贝、浅拷贝
- 预解析
- 字符串
- 浏览器的同源策略( 跨域 )
- 为什么利用多个域名来存储网站资源会更有效
- 如何解决跨域
- 项目优化
- 什么是单线程,和异步有什么关系
- attribute 和 property 的区别
- WebWorker
- call、apply、bind 区别
- sort 背后的原理
- null 和 undefined 的区别
- == 和 === 有什么不同
- slice 和 splice 的区别
- typeof 和 instanceof 区别
- ES5 继承和 ES6 继承 的区别
- 异步编程解决方案
- 22. defer 和 async
- JavaScript 中执行上下文和执行栈是什么
- ajax 原理
- 正则表达式
- async 与 await
- cookie 、sessionStorage 和 localStorage 区别
- 尾递归
- 说说 JavaScript 数字精度丢失的问题
- 如何判断一个元素是否在可视区域中
- 大文件上传如何做断点续传
- 如何实现上拉加载、下拉刷新
- 什么是单点登录
- web 常见的攻击方式有些
- Object 类
- link 和 import 的区别
javascript 汇总
Javascript 由三部分组成
- ECMAScript 语法
- DOM 页面文档对象模型
- BOM 浏览器对象模型
继承, 继承可以使得子类别具有父类的的各种方法和属性(六种)
- 原型类继承
// 每个构造函数都有一个原型对象,原型都有一个原型对象,原型对象又包含一个指向构造函数的指针,而实例则包含一个原型对象的指针
function Parent(){
this.name = 'parent'
this.play = [1,2,3]
}
function Child(){
this.type = 'child'
}
Child.prototype = new Parent()
var c1 = new Child()
var c2 = new Child()
c1.play.push(4)
// c1 、 c2 实例使用的是同一个原型对象,内存空间是共享的
console.log(c1.play,c2.play) // [1,2,3,4] [1,2,3,4]
- 构造函数继承(借助call)
// 构造函数继承
// 只能继承父类的实例属性和方法,不能继承原型属性或者方法
function Parent(){
this.name = 'parent'
}
Parent.prototype.getName = function(){
return this.name
}
function Child(){
Parent.call(this)
this.type = 'child'
}
let child = new Child()
console.log(child) // 没问题
console.log(child.getName()) // 会报错
- 组合继承
// Parent 执行了两次,造成了多构造一次的性能开销
function Parent(){
this.name = 'parent'
this.play = [1,2,3]
}
Parent.prototype.getName = function(){
return this.name
}
function Child(){
Parent.call(this)
this.type = 'child'
}
// 第一次调用 Parent()
Child.prototype = new Parent()
// 手动挂上构造器,指向自己的构造函数
Child.prototype.constructor = Child
var c1 = new Child()
var c2 = new Child()
c1.play.push(4)
console.log(c1.play,c2.play) // 互不影响
console.log(c1.getName()) // parent
console.log(c2.getName()) // parent
- 原型式继承
// Object.create 方法是浅拷贝
let parent = {
name: 'parent',
friends: ['p1','p2','p3'],
getName: function(){
return this.name
}
}
let child1 = Object.create(parent)
child1.name = 'zhangsan'
child1.friends.push('lisi')
let child2 = Object.create(parent)
child2.friends.push('wangwu')
console.log(child1.name) // zhangsan
console.log(child1.name === child1.getName()) // true
console.log(child2.name) // parent
console.log(child1.friends) // [ 'p1', 'p2', 'p3', 'lisi', 'wangwu' ]
console.log(child2.friends) // [ 'p1', 'p2', 'p3', 'lisi', 'wangwu' ]
- 寄生式继承
// Object.create 方法是浅拷贝
let parent = {
name: 'parent',
friends: ['p1','p2','p3'],
getName: function(){
return this.name
}
}
function clone(obj){
let clone = Object.create(obj)
clone.getFriends = function(){
return this.friends
}
return clone
}
let child = clone(parent)
console.log(child.getName()) // parent
console.log(child.getFriends()) // [ 'p1', 'p2', 'p3' ]
- 寄生组合方式继承
// 最优
function clone(parent,child){
child.prototype = Object.create(parent.prototype)
child.prototype.constructor = child
}
function Parent(){
this.name = 'parent'
this.play = [1,2,3]
}
Parent.prototype.getName = function(){
return this.name
}
function Child(){
Parent.call(this)
this.friends = 'child'
}
clone(Parent,Child)
Child.prototype.getFriends = function(){
return this.friends
}
let c1 = new Child()
console.log(c1) // Child { name: 'parent', play: [ 1, 2, 3 ], friends: 'child' }
console.log(c1.getName()) // parent
console.log(c1.getFriends()) // child
闭包
「函数」和「函数内部能访问到的变量」的总和,就是一个闭包。
可以在一个内层函数中访问到其外层函数的作用域
使用场景:
- 创建私有变量
- 延长变量的生命周期
在浏览器中从输入Url并回车发生了什么?
- 输入网址
- 发送到 DNS 服务器域名解析,获得真实IP地址
- 与服务器建立 TCP 连接
- 浏览器向 web 服务器发送 HTTP 请求
- 服务器处理请求,并返回响应结果(指定 URL 数据,或者错误信息,或重定向新的 URL 地址)
- 浏览器下载 web 服务器返回的数据及解析 html 源文件
- 生成 DOM 树,解析 css 和 js ,渲染页面,直至显示完成
XSS 跨域脚本注入
CSRF(跨站请求伪造)
https://blog.csdn.net/stpeace/article/details/283
(1)验证 HTTP Referer 和 Origin 字段
(2)在请求地址中添加 token 并验证
(3)在 HTTP 头中自定义属性并验证
Referer
https://blog.csdn.net/shenqueying/article/details/79426884
原生javaScript
DOM 操作
- 创建节点 :createElement
- 查询节点:queryELement
- 更新节点:innerHTML
- 添加节点:appendChild
- 删除节点:removeChild
获取DOM
console.dir("对象")
document.getElementById("id值")
document.getElementsByTagName("标签名")
document.getElementsByClassName("类名")
document.querySelector("选择器")//返回指定的第一个元素对
document.querySelectorAll("")
document.body
document.documentElement
排他算法
先干掉所有人,然后操作指定元素
阻止链接跳转
<a href="javascript:void(0);">单击此处什么都不会发生</a><br>//阻止链接跳转 javascript:void(0); 或者 javascript:;
获取自定义属性
element.getAttribute("属性")
element.setAttribute("属性","值")
element.removeAttribute("属性")
// H5新增获取自定义属性,只能以 data- 开头的,IE 11开始支持,如果自定义属性里有多个 - 单词,获取的时候采用驼峰命名法
#data-index data-index-name
element.dataset.index
element.dataset.indexName
element.dataset['index']
element.dataset['indexName']
// scrollIntoView()方法将调用它的元素滚动到浏览器窗口的可见区域
element.scrollIntoView
节点
创建元素的三种方式
- document.write() //不推荐,会导致页面重绘
- element.innerHTML() //不要拼接字符串,采用数组形式效率高
- document.createElement()
节点类型
nodeType(节点类型)、nodeName(节点名称)、nodeValue(节点值)
// 输出节点名称,需要先判断节点类型
nodeType = 1 //元素节点
nodeType = 2 //属性节点
nodeType = 3 //文本节点(文字、空格、换行)
父子兄层级关系
//父子节点 : node.parentNode 离它最近的父级节点,找不到返回null
parentNode.childNodes //返回的是所有节点(包含属性节点 和 文本节点)**不提倡**
parentNode.children //获取所有的子元素节点
//获取第一个和最后一个节点
parentNode.firstChild //获取第一个节点,不推荐(包含属性节点 和 文本节点)
parentNode.lastChild //获取最后一个节点
//要求兼容性 IE 9 以上
parentNode.firstElementChild //获取第一个元素节点,
parentNode.lastElementChild //获取的最后一个元素节点, IE 9 以上
//开发过程中,获取第一个和最后一个节点实际写法
node.children[0]
node.children[node.children.length - 1]
//兄弟节点
node.nextSibling //下一个节点包含 元素 节点或者 文本节点
node.previousSibling //上一个节点
//要求兼容性 IE 9 以上
node.nextElementSibling //下一个元素节点
node.previousElementSibling //上一个元素节点
//实际过程中,自己封装一个函数
function getNextElementSibling(element) {
var el = element;
while (el = el.nextSibling) {
if(el.nodeType === 1){
return el ;
}
}
return null;
}
节点操作
//添加节点------1.创建元素节点,2.添加到页面上
var newEl = document.createElement("")
//在节点后插入
node.appendChild(child)//node 父级 child 子级,类似 数组 push
parent.appendChild(newEl)
//在节点之前插入
node.insertBefore(child,node)
//删除节点
mode.removeChild(child)//node 父级 child 子级,
<!--复制节点----1.复制节点,2.添加到页面上
false 浅拷贝,只复制标签,不复制里面的内容;
true 深拷贝,复制标签和复制里面的内容 -->
node.cloneNode(Boolean)
事件(事件源、事件类型、事件处理类型)
1.获取事件源
2.注册事件,绑定事件
3.添加事件处理程序
传统注册事件
1.利用on开头的事件
- btn.onclick = function(){}
- 特点:注册事件的唯一性,后面的注册处理函数会覆盖前面的注册处理函数
- 事件源.事件类型 = 事件方法,
2.方法监听注册事件 (w3c标注 推荐方式)
- eventTarget.addEventListener(type,listener[,canUpdate])
- 特点:同一个元素,同一个事件可以注册多个监听器 ,多个事件处理程序
- type 事件类型字符串,不带on
- listener 事件处理函数
- seCapture:可选参数,布尔值,默认为false,表示在事件冒泡阶段调用事件处理程序(从最外层到最内层);如果为true,则表示在事件捕获阶段调用事件处理程序(从最内层到最外层)
3.淘汰的 attachEvent()非标准,不建议在生产环境中使用
- eventTarget.attachEvent(eventNameWithOn,callback)
- eventNameWithOn 事件类型字符串要带上on
- callback 事件处理函数
// 删除事件
eventTarget.onclick = function(){}
eventTarget.onclick = null
<!-- 推荐写法 -->
eventTarget.addEventListener('click',fn)//fn不需要()调用
eventTarget.removeEventListener('click',fn[,canUpdate])
eventTarget.attachEvent('onclick',fn)
eventTarget.detachEvent('onclick',fn)
冒泡和捕获
DOMException 事件流,事件发生时会在元素节点之间按照特定的顺序传播的传播过程
标准事件模型,事件流描述的是从页面中接收事件的顺序,可以分成三个阶段
1.捕获阶段;
2.当前目标阶段;
3.冒泡阶段
事件冒泡:IE 最早提出,事件开始由最具体的元素接收,然后逐级向上传播到DOM最顶层的过程
事件捕获: 网景最早提出,由DOM最顶层节点开始,然后主机向下传播到具体元素接收的过
1.JS代码中只能执行捕获或者冒泡的其中一个阶段
2.onclick 和 attachEvent 只能得到冒泡阶段
3.addEventListener(type,listener[,canUpdate]),第三个参数==true 表示捕获阶段,==false,默认不写就是false 表示冒泡阶段
4.实际开发过程中很少用到捕获,我们只需要关注冒泡
5.有些事件是没有冒泡的,比如 onblur,onfocus,onmouseenter,onmouseleave
事件对象
function(event)
1.event就是一个事件对象,写到我们的侦听函数里,写在小括号里,当形参看
2.事件对象只有有了事件才会存在,系统自动会创建,不需要我们传递参数
3.事件对象 是 我们事件的一系列相关数据的集合
4.事件对象可以自己命名
5.事件对象也有兼容性问题,IE 6 7 8 通过 window.event, 兼容性写法 e = e || window,event常见的事件对象和属性。
e.target 返回的是触发事件的对象(元素),(点击了哪个元素,就返回了那个元素), this 返回的是绑定事件的对象(元素)
e.srcEleMent (IE 6 7 8) === e.target
兼容性写法
div.onclick = function(e){
e = e || window.event
var target = e.target || e.srcElement
}
currentTarget 跟 this 很相似,但是IE 6 7 8 不认识
e.type 事件类型
阻止事件的默认行为
e.preventDefault() //方法,组织时间默认行为,DOM 标准写法
e.returnValue; //属性,只支持IE 6 7 8
return false //没有兼容
阻止事件冒泡
标准写法: 利用事件对象里面的 e.stopPropagation()方法,IE 6 7 8 不支持
非标准 e.cancelBubble = true
事件委托、委派 click 、mousedown 、mouseup、kedown、keyup、keypress
不是每个子节点单独设置事件监听器,而是事件监听器设置在起父节点上,然后利用冒泡原理影响设置每个子节点
作用 : 减少操作DOM次数,提高程序性能
- 减少整个页面所需的内存,提升整体性能
- 动态绑定,减少重复工作
例子(e.target 获取当前点击的元素对象):
<ul>
<li>点击1</li>
<li>点击2</li>
<li>点击3</li>
<li>点击4</li>
<li>点击5</li>
</ul>
<script>
//使用事件委托的代码
var ul = document.getElementsByTagName('ul')[0];
ul.addEventListener('click', function(e){
alert(e.target.innerHTML)
}, false);
//不使用事件委托,循环给li添加click事件
var li = document.getElementsByTagName('li');
for(var i = 0; i < li.length; i++){
li[i].onclick = function () {
alert(this.innerHTML);
}
}
</script>
事件模型
- 原始事件模型(DOM0级别)直接绑定或者通过 js 绑定 onclick
- 标准事件模型 (DOM2 级别)事件监听函数 addEventListener
- IE 事件模型 (基本不用)
鼠标事件
contextmenu 禁止右键菜单
selectstart 禁止选中文字
鼠标坐标
client 鼠标在浏览器可视区域X,Y
page 文档页面的X,Y IE9+ 支持
screen 电脑屏幕
键盘事件
- keyup //不区分大小写,文字已经在输入框里面
- keydown //不区分大小写
- keypress //区分大小写,不识别功能键
执行顺序: keyup => keypress => keydown
BOM 浏览器对象模型
window =>( document location navigator screen history)
- location对象提供了与当前窗口中加载的文档有关的信息以及一些导航功能;
- navigator对象用来描述浏览器本身,包括浏览器的名称、版本、语言、系统平台、用户特性字符串等信息;
- screen对象用来表明客户端显示器的能力。多用于测定客户端能力的站点跟踪工具中。
- history对象保存着从窗口被打开起的历史记录,每个浏览器窗口、标签页、框架都有自己的history对象;
window.name => 是一个特殊属性,所以 命名变量的时候不推荐 变量名name
window.onload => 是窗口(页面)加载事件,当文档内容完全加载完成后会触发该事件,包括样式表、图标,只能执行一次,以最后一个为准
window.addEventListener(‘load’,fn) 没有限制个数
document.addEventListener(‘DOMContentLoaded’,fn) //当DOM 加载完毕后完成,不包括样式表、图片,IE9以上
resize 浏览器窗口大小事件,window.innerWidth 当前浏览器窗口宽度
定时器
setTimeout(callback,3000)//定时炸弹
clearTimeout()
setInterval()// 反复调用
clearInterval()
js 执行机制(单线程)
同步和异步
- 同步任务: 同步任务都在主线程上执行,形成一个执行栈
- 异步任务:通过回调函数实现,会把相关回调函数添加到任务队列中(消息队列)
- 普通事件,如click、resize
- 资源加载,如load、error
- 定时器
执行机制:
- 先执行 执行栈中的同步任务
- 异步任务(回调函数)放入任务队列中
- 一旦执行栈中的所有同步任务执行完毕,系统会按次序读取任务队列中的异步任务,于是被读取的异步任务结束等待状态,进入执行栈,开始执行
event loop 事件循环
由于主线程不断地重复获得任务、执行任务、再获取任务、再执行,所以这种机制叫做 事件循环
ES6事件循环
JS是有两个任务队列的,一个叫做宏任务,一个叫做(微任务)
Macrotask Queue(宏观任务): 在浏览器端,其可以理解为该任务执行完后,在下一个macrotask执行开始前,浏览器可以进行页面渲染。触发macrotask任务的操作包括
- script(整体代码)
- setTimeout、setInterval、setImmediate
- I/O、UI交互事件
- postMessage、MessageChannel
Microtask Queue(微观任务):可以理解为在macrotask任务执行后,页面渲染前立即执行的任务。触发microtask任务的操作包括:
- Promise.then
- MutationObserver
- process.nextTick(Node环境)
- await
console.log('script start');
setTimeout(function() {
console.log('timeout');
}, 0);
Promise.resolve().then(function() {
console.log('promise1');
}).then(function() {
console.log('promise2');
});
console.log('script end');
//输出结果
// script start > script end > promise1 > promise2 > timeout
其实事件循环做的事情如下:
- 执行一个macrotask(包括整体script代码),若js执行栈空闲则从任务队列中取
- 执行过程中遇到microtask,则将其添加到micro task queue中;同样遇到macrotask则添加到macro task queue中
- macrotask执行完毕后,立即按序执行micro task queue(微任务队列)中的所有microtask;如果在执行microtask的过程中,又产生了microtask,那么会加入到队列的末尾,也会在这个周期被调用执行;
- 所有microtask执行完毕后,浏览器开始渲染,GUI线程接管渲染
- 渲染完毕,从macro task queue(宏任务队列)中取下一个macrotask开始执行
简单来讲,整体的js代码这个macrotask先执行,同步代码执行完后有microtask执行microtask,没有microtask执行下一个macrotask,如此往复循环;
location 对象
统一资源定位符 URL
location.href //获取或者设置整个URL
location.host //获取主机(域名)
location.port //获取端口号
location.pathname //返回路径
location.search //返回参数
location.hash //返回片段
location.assign() //跳转页面,记录浏览器历史,可后退
location.replace() //重定向,不计入历史不可后退
location.reload() //页面重载,可以加 true 强制刷新
navigator 对象
浏览器的信息 userAgent 浏览器类型
history 对象
history.back() //后退
history.forward() //前进
history.go() //前进后退功能,1前进一个页面,-1后退一个页面
history.length 获取历史记录数
元素偏移量 offset 元素的位置、偏移
element.offsetTop 、element.offsetLeft// 以带有定位的父亲元素为准,找不到父亲,返回body,不返回单位,
element.offsetWidth、elementHeight 包括padding、broder、内容区的宽度高度
offset 和 style 区别
offset | style |
---|---|
可以得到任意样式表中的样式值 | 只能得到行内样式表中的样式值 |
数值没有单位 | .数值有单位字符串 |
offset.Width 包含padding、broder、width; | 只有width |
offset.Width等属性只读,不能赋值 | style.width 可读写 |
适合获取元素大小位置 | 适合改变元素的大小位置,client 元素边框大小、元素大小、不包含边框 |
数组
会改变原数组:push\pop、shift\unshift、sort\reverse、splice
不会改变元素组: map、forEach、filter、some、every、join、concat、slice、indexOf、reduce
遍历数组
// for 最优写法
for(var i , len = arr.length; i<len; i++){
//耗时最短,性能最高
}
arr.forEach(function(e){
//ES 5 加上的
//数组自带的方法,不推荐
})
for(var i in arr){
//多用于遍历对象,性能低下,不推荐
}
arr.map(function(e){
//好看不实用,性能比forEach还低
})
for(let value of arr){
//需要ES6 支持,性能好于 for in ,但比不上普通的for循环
}
// every (return false 跳出循环,return true 也需要写)
//当内部return false时跳出整个循环
let list = [1, 2, 3, 4, 5];
list.every((value, index) => {
if(value > 3){
console.log(value)// 4
return false;
}else{
console.log(value)// 1 2 3
// 如果没有return true 的话,直接输出 1 后,跳出循环
return true;
}
});
// some( return true 跳出整个循环)
let list2 = [1, 2, 3, 4, 5];
list2.some((value, index) => {
if(value === 3){
return true;//当内部return true时跳出整个循环
}
console.log(value)// 1 2
});
// for of 遍历数组
// for in 遍历对象
for 循环和 for in 、for of 能正确的响应 break、continue 和 return ,forEach 不行
遍历对象
对象如何找属性 | 方法:
先在对象本身找 => 构造函数中找 => 对象原型中找 => 构造函数原型中找 => 对象上一层原型查找
var mySymbol = Symbol()
var obj = {
0 : 'a',
1 : 'b',
2 : 'c'
}
// 可枚举属性
Object.defineProperty(obj,'4',{
value: 'IS123',
enumerable: true
})
//定义不可枚举属性
Object.defineProperty(obj,'person',{
value: '123',
enumerable: false
})
//定义symbol 属性
obj[mySymbol] = 'hello';
//1. Object.keys() 返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含Symbol属性)
Object.keys(obj).forEach((key)=>{
console.log(key,obj[key]);
})
// 0 a
// 1 b
// 2 c
// 4 IS123
//2. for in 遍历 循环遍历对象自身的和继承的可枚举属性(不含Symbol属性)
for(var i in obj){
console.log(i, ':', obj[i]);
}
// 0 a
// 1 b
// 2 c
// 4 IS123
//3. Object.getOwnPropertyName(obj) 返回一个数组,包含对象自身的所有属性(不含Symbol属性,但是包括不可枚举属性)
Object.getOwnPropertyNames(obj).forEach((key)=>{
console.log(key,obj[key]);
})
// 0 a
// 1 b
// 2 c
// 4 IS123
// 4. Reflect.ownKeys(obj) 返回一个数组,包含对象自身的所有属性,不管属性名是Symbol或者字符串,也不管是否可枚举
Reflect.ownKeys(obj).forEach((key)=>{
console.log(key,obj[key]);
})
// 0 a
// 1 b
// 2 c
// 4 IS123
// person 123
// Symbol() 'hello'
作用域
作用域,即变量(变量作用域又称 上下文)和函数生效(能被访问)的区域或集合
1.在函数内部没有声明直接赋值的变量属于全局变量
2.全局变量:在任何一个地方都可以使用,只有在浏览器关闭时才会被销毁,因此比较占内存
3.局部变量:只在函数内部使用,当其多所在的代码块被执行时,会被初始化;当代码块运行结束后,就会被销毁,因此就更节约内存空间
作用域链
作用域链:一组对象列表,包含 父级和自身的变量对象,因此可以通过作用域链访问到父级 ( 逐层查找 ) 里声明的变量或者函数。( 就近原则 )
原型、原型链、继承
- 原型: 所有的函数都有 prototype 属性,所有的对象都有 proto 属性
- 在 JavaScript 中,每个函数都有一个原型属性 prototype 指向自身的原型,而由这个函数创造的对象也有一个 proto 属性指向这个原型,而函数的原型是一个对象,所以这个对象也会有一个 proto 指向自己的原型,这样逐层深入到 Object 对象原型,这样就形成了原型链。
JS 垃圾回收机制
- JS 的垃圾回收机制是为了防止内存泄漏( 已经不需要的某一块内存还一直存在着 ),,垃圾回收机制就是不停歇的寻找这些不再使用的变量,并且释放掉它所指向的内存
- 变量的生命周期: 当一个变量的生命周期,它所指向的内存就会被释放。JS 有两种变量,局部变量和全局变量,局部变量是在他当前的函数中产生作用,当该函数结束之后,该变量内存会被释放,全局变量则会一直存在,直到浏览器关闭为止。
- JS 垃圾回收方式:
**标记清除:**大部分浏览器使用这种垃圾回收,当变量进入执行环境(声明变量)的时候,垃圾回收器将该变量离开环境的时候,将其再度标记,随之进行删除。
**引用计数:**这种方式常常会引起内存泄露,主要存在于低版本的浏览器。它的机制就是跟踪一个值得引用次数,当声明一个变量并且将一个引用类型赋值给变量的时候引用次数加 1 ,当这个变量指向其他一个时引用次数减 1 ,当为 0 时触发回收机制进行回收
函数表达式(匿名函数)
var fn = function(){}
this
- this 总是指向函数的直接调用者
- 如果有 new 关键字,this 指向 new 出来的对象
- 在事件中,this 指向这个事件的对象
绑定规则
- 默认绑定
- 隐式绑定
- new 绑定
- 显式绑定
优先级: new 绑定 > 显式绑定 > 隐式绑定 > 默认绑定
this 指向问题
- 全局作用域或者普通函数中 this 指向是全局对象 window (定时器中的this也是执行window)
- 方法调用中谁调用 this 指向谁
- 构造函数中 this 指向构造函数的实例
- 箭头函数: 箭头函数的 this 绑定看的是 this 所在函数定义在哪个对象下,就绑定哪个对象。如果有嵌套的情况,则 this 绑定到最近的一层对象上
- apply 、call、bind 可以改变 this 的指向。
New 构造函数
- 创建一个新对象;
- 新对象会被执行 [[ prototype ]] 原型连接 ( proto => prototype )
- 新对象和函数调用的 this 会被绑定起来
- 执行构造函数中的代码
- 如果函数没有返回值,就自动返回这个新对象
深拷贝、浅拷贝
基本数据类型(名值存储于栈内存中):string、number、boolean、null、undefined、symbol、BigInt(ES10)
引用数据类型(名在栈内存中,值存在于堆内存中,但是栈内存会提供一个引用的地址指向堆内存中的值):有常规名值的无需对象{a:1}、数组[1,2,3]、函数
- 拷贝对象变了,浅拷贝
- 不影响拷贝对象,深拷贝
- 可以使用 lodash 插件实现深拷贝( _.cloneDeep() )
数组可用 JSON.stringify()和JSON.parse()实现深拷贝
预解析
字符串
// substring(start,end) 用数学表达式表达区间的话就是截取[start,end);
// substring(start) 没有end相当于[start,最后一个字符];
let str = "hello world"
console.log(str.substring(0,3)) // hel
console.log(str.substring(3)) // lo world
// slice(start,end) -> start不能大于end,否则返回空字符串;
// slice可以接受参数是负数,如果是负数的话,规则将按照:字符串的长度和赋值相加,替换掉这个值。举例如下:
console.log(str.slice(0,3)) // hel
console.log(str.slice(3)) // lo world
console.log(str.length)
console.log(str.slice(11)) // 空字符串
console.log(str.slice(1,2))
console.log(str.slice(1,-9)) // e, 等价于 str.slice(1,(11-9)) === str.slice(1,2)
console.log(str.slice(7,-10)) // 空字符串,等价于 str.slice(7,(11-10)) === str.slice(7,1)
// 已经废弃
// substr(start,length) -> 截取的字符串区间为:[start,start+length)->从start开始,算上start数length个字符串;
// substr(start) -> 截取的字符串区间为:[start,最后一个字符]
console.log(str.substr(0,2)) // he
// 字符串转数组
console.log(str.split(''))
let arr = str.split('')
// 数组转字符串
console.log(arr.join(''))
浏览器的同源策略( 跨域 )
1.协议 ( protocol )
2.域名 ( host )
3.端口号 ( port )
为什么利用多个域名来存储网站资源会更有效
1.CDN 缓存更方便
2. 突破浏览器并发限制
3. 节约 cookice 带宽
4. 节约主域名的连接数,优化页面响应速度
5. 防止不必要的安全问题
如何解决跨域
- jsonp 跨域
- document.domain + iframe 跨域
- node.js 中间件代理跨域 (服务器代理)
- 后端在头部信息里设置安全域名 (CORS)【Access-Control-Allow-Origin】
项目优化
减少 HTTP 请求
减少 DNS 查询
使用 CDN
避免重定向
图片懒加载
减少 DOM 元素数量
减少 DOM 操作
使用外部 JavaScript 和 CSS
压缩 JavaScript 、CSS、字体、图片
优化 CSS Sprite
使用 iconfont
多域名分发划分内容到不同域名
尽量减少 iframe 使用
避免图片 src 为空
把样式表放在 link 中
把 JavaScript 放在页面底部
什么是单线程,和异步有什么关系
单线程:只有一个线程,只能做一件事
原因: 避免 DOM 渲染的冲突
浏览器需要渲染 DOM,JS 可以修改 DOM 结构,JS 执行的时候,浏览器 DOM 渲染会停止,两段 JS 也不能同时执行( 都修改 DOM 就冲突了 )
webworker 支持多线程,但是不能访问 DOM
解决方案: 异步
attribute 和 property 的区别
attribute 是 DOM 元素在文档中作为 html 标签拥有的属性
property 是 DOM 元素在 js 中作为对象拥有的属性
对于 html 的标准属性来说,attribute 和 property 是同步的,是会自动更新的
WebWorker
在后台运行的 Javascript ,独立于其他脚本,不会影响页面性能。(多线程)
// index.html
const worker = new worker('worker.js')
worker.onmessage = e =>{
console.log(e)
}
// worker.js
self.postMessage('hello worker!!')
call、apply、bind 区别
- 三者都可以改变函数的 this 对象指向
- 三者的第一个参数都是 this 要指向的对象,如果没有这个参数或参数为 undefined 或 null ,则默认指向全局 window
- apply 第二个参数是数组,call 和 bind 有多个参数需要挨着写
- call、apply 可以立即执行。bind 是返回绑定 this 之后的函数,不会立即执行
sort 背后的原理
现在是冒泡排序,之前是插入排序和快排
null 和 undefined 的区别
- 作者在设计 js 的时候都是先设计的 null (最初设计 js 的时候借鉴了 java )
- null 会被隐式转换成 0 ,不容易发现错误
- 先有的 null 后有的 undefined
Javascript 的最初版本是这样区分的:null 是一个表示 “无” 的对象(空对象指针),转换数值时为 0 ;undefined 是一个表示未定义的值,转为数值时为 NaN
== 和 === 有什么不同
相等操作符(==)比较规则:
- 两个都为简单类型,字符串和布尔值都会转换成数值,再比较
- 简单类型与引用类型,对象转化成其原始类型的值,再比较
- 两个都为引用类型,则比较它们是否指向同一个对象
- null 和 undefined 相等
- 存在 NaN 则返回 false
区别:
- == 等于操作符,比较的时候会隐式转换(通过 valueOf() 方法转换 )
- === 全等操作符,类型相同,值也相同
除了在比较对象属性为 null 或者 undefined 的情况下,使用 相等操作符,其他的情况都使用 全等操作符
slice 和 splice 的区别
- slice 只能是用来截取的
参数可以是 slice(1)、slice(1,3)、slice(-1)
返回的是一个新的数组 - splice 功能有: 插入、删除、替换
返回的是改变元素后的数组
typeof 和 instanceof 区别
typeof 操作符返回一个字符串,表示未经计算的操作数的类型
instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上
区别:
- typeof 会返回一个变量的基本类型,instanceof 返回的是一个布尔值
- instanceof 不能准确判断
- typeof 不能判断 null ,引用类型除(function)之外,其他的也不能准确判断
typeof | instanceof | |
---|---|---|
作用 | 检测数据类型 | 检测对象之间的关联性 |
返回 | 小写字母字符串 | 布尔值 |
操作数 | 简单数据类型、函数或者对象 | 左边必须是引用类型右边必须是函数 |
操作数数量 | 1个 | 2个 |
ES5 继承和 ES6 继承 的区别
- ES5 继承的实质是先创建子类的实例对象,然后再将父类的方法添加到 this 上 ( Parent.apply( this ) ), 然后再原型链继承
- ES6 继承是先创建父类的实例对象( 所以必须调用父类的 super() 方法,才可使用 this 关键字,否则报错),然后再用子类的构造函数修改 this 实现继承
异步编程解决方案
- 回调函数
- Promise 对象
- Generator 对象
- async / await
区别: - Promise 和 async/await 是专门用于处理异步操作的
- Generator 并不是为异步而设计出来的,它还有其他功能(对象迭代、控制输出、部署 interator 接口…)
- Promise 编写代码相比 Generator 、async 更为复杂化,且可读性也稍差
- Generator 、async 需要与 Promise 对象搭配处理异步情况
- async 实质上 Generator 的语法糖,相当于会自动执行 Generator 函数
- async 使用上更为简洁,将异步代码以同步的形式进行编写,是处理异步编程的最终方案
22. defer 和 async
defer 阻塞,文档解析完毕后执行
async ,html5 新属性,异步下载脚本,下载完毕后立即解释执行代码
JavaScript 中执行上下文和执行栈是什么
简单的来说,执行上下文是一种对 JavaScript 代码执行环境的抽象概念
分成三种类型:
- 全局执行上下文:JavaScript 代码运行起来会首先进入该环境
- 函数执行上下文:当函数被调用执行时,会进入当前函数中执行代码
- Eval 函数执行上下文 (不建议使用,可忽略)
生命周期:创建阶段------》 执行阶段-----》 回收阶段
执行栈,也叫调用栈,具有 LIFO ( 后进先出 )结构,用于存储在代码执行期间创建的所有执行上下文 - 创建全局上下文请压入栈
- first 函数被调用,创建函数执行上下文并压入栈
- 执行 first 函数过程遇到 second 函数,在创建一个函数执行上下文并压入栈
- second 函数执行完毕,对应函数执行上下文被推出执行栈,执行下一个执行上下文 first 函数
- frist 函数执行完毕,对应的函数执行上下文也被推出执行栈,然后执行全局上下文
- 所有代码执行完毕,全局上下文也会推出栈,程序结束
ajax 原理
Ajax 通过 XmlHttpRequest 对象服务器发异步请求,从服务器获得数据,然后用 JavaScript 来操作 DOM 而更新页面
过程:
- 创建 Ajax 和核心对象 XMLHTTPrequest 对象
- 通过 XMLHTTPrequest 对象的 open() 方法与服务器简历连接
- 构建请求所需的数据内容,并通过 XMLHTTPRequest 对象的 send() 方法发送给服务端
- 通过 XMLHTTPRequest 对象提供的 onreadystatechange 事件监听服务器端的通信状态
- 接受并处理服务器端向客户端响应的数据结果
- 处理结果更新到 HTML 页面中
正则表达式
正则表达式用来匹配字符串
async 与 await
function f() {
return Promise.resolve('test')
}
// 等价于
async function asyncF() {
return 'test'
}
console.log(f(), asyncF())
// Promise { 'test' } Promise { 'test' }
不管 await 后面跟着什么,await 都会阻塞后面的代码
async function fn1 (){
console.log(1)
await fn2()
console.log(2) // 被阻塞
}
async function fn2 (){
console.log('fn2')
}
fn1()
// 1 fn2 2
cookie 、sessionStorage 和 localStorage 区别
cookie | sessionStorage | localStorage | |
---|---|---|---|
大小 | 4kb | 5MB | 10MB |
兼容 | H4/H5 | H5 | H5 |
访问 | 任何窗口 | 同一窗口 | 任何窗口 |
有效期 | 手动设置 | 到窗口关闭 | 无 |
存储位置 | 浏览器或者服务器 | 浏览器 | 浏览器 |
与请求一起发送 | 是 | 否 | 否 |
语法 | 复杂 | 简易 | 简易 |
尾递归
递归:需要有边界条件、递归前进阶段和递归返回阶段
尾递归,多了两个特征:
- 在尾部调用的是函数自身
- 可通过优化,使得计算仅占用常量栈空间
// 递归
function factorial(n) {
if (n === 1) return 1;
return n * factorial(n - 1)
}
// 尾递归
function factorialNew(n, total = 1) {
if (n === 1) return total
return factorialNew(n - 1, n * total)
}
console.log(
factorial(5),
factorialNew(5)
) // 120 120
说说 JavaScript 数字精度丢失的问题
toPrecision 凑整并 parseFloat
console.log(parseFloat(2.80000000000000000002.toPrecision(12)) === 2.8)
如何判断一个元素是否在可视区域中
实现方式:
- offsetTop、scrollTop
- getBoundingClientRect
- Intersection Observer
大文件上传如何做断点续传
分片上传:
- 将需要上传的文件按照一定的分割规则,分割成相同大小的数据块
- 初始化一个分片上传任务,返回本次分片上传唯一标识
- 按照一定的策略(串行或并行)发送各个分片数据块
- 发送完成后,服务端根据数据上传是否完整,如果完整则进行数据块合成得到原始文件
断点上传:
每一个部分采用一个线程进行上传或下载,如果碰到网络故障,可以从已经上传或者下载的部分开始继续上传或下载未完成的部分,而没有必要从头开始上传下载。用户可以节省时间,提高速度
如何实现上拉加载、下拉刷新
触底公式:scrollTop + clientHeight >= scrollHeight
- scrollTop : 滚动视窗的高度距离 window 顶部的距离,它会随着往上而不断增加,初始值是 0 ,它是一个变化的值
- clientHeight: 它是一个定值,标识屏幕可视区域的高度
- scrollHeight:页面不能滚动时是不存在的,body 长度超过 window 时才会出现,所表示 body 所有元素的长度
下拉刷新:
- 监听原生 touchstart 事件,记录其初始位置,e.touches[0].pagey
- 监听原生 touchmove 事件,记录并计算出当前滑动的位置与初始位置的差值,大于 0 表示向下拉动,并借助 CSS3 的 translateY 属性使元素跟随手势往下滑动对应的差值,同时也应设置一个允许滑动的最大值
- 监听原生的 touchend 事件,若此时元素滑动达到最大值,则触发 callback ,同时 translateY 重设为 0,元素回到初始位置
什么是单点登录
SSO 是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统
实现:
1. 同域名下的单点登录:
cookie 的 domain 属性设置为当前域的父域,并且父域的 cookie 会被子域所共享,path 属性默认为 web 应用 的上下文路径。
只要将 Cookie 的 domain 属性设置为父域的域名(主域名),同时将 Cookie 的 path 属性设置为根路径,将 Session ID (或 Token )保存到父域中。这样所有的子域应用就可以访问到这个 Cookie
不过这要求应用系统的域名需要建立在一个共同的主域名之下。
2. 不同域名下的单点登录 (一)
部署一个认证中心,用于专门处理登录请求的独立 web 系统
3. 不同域名下的单点登录 (二)
可以选择将 Session ID (或者 Token )保存到浏览器的 localstorage 中,让前端在每次向后端请求时,主动加 localstorage 的数据传输给服务端
具体实现:前端通过 iframe + postMessage() 方式,将同一份 Token 写入到多个域的 localstorage 中。
web 常见的攻击方式有些
- XSS (Cross site script)跨站脚本攻击
- CSRF (Cross-site request forgery)跨站请求伪造
- SQL 注入攻击
XSS:
- 存储型: 存到数据库
- 反射型:存到 URL
- DOM 型: 恶意代码由浏览器执行
预防:
主要通过过滤
CSRF
攻击者诱导受害者进入第三方网站,在第三方网站中,向被攻击者发送跨站请求。利用受害者在被攻击网站已经获取的注册凭证,绕过后台的用户验证,打到冒充用户对被攻击者的网站执行某项操作目的
特点:
- 攻击一般发起在第三方网站,而不是被攻击的网站。被攻击的网站无法防止攻击的发生;
- 攻击利用受害者在被攻击网站的登录凭证,冒充受害者提交操作;而不是直接窃取数据;
- 整个过程攻击者并不能获取到受害者的登录凭证,仅仅是‘冒用’
- 跨站请求可以用各种方式: 图片URL 、超链接、CORS、From 提交等等。部分请求方式可以直接嵌入在第三方论坛、文章中。难以进行追踪
预防 - 阻止不明外域的访问: 同源检测;Samesite Cookie
- 提交是要求附加本域才能获取信息:CSRF Token;双重 Cookie 验证
SQL 注入
- 严格检查输入变量的类型和格式
- 过滤和转移特殊字符
- 对访问数据库的 web 应用程序采用 web 应用防火墙
Object 类
属性名 | 定义 | 默认值 |
---|---|---|
writable | 属性是否可以被重写 | false |
enumerable | 属性是否可以被枚举 | false |
configurable | 属性是否可以被再次修改 | false |
Object.assign() : 用于将所有可枚举属性的值从一个或者多个源对象分配到目标对象
Object.create() : 创建一个新对象,使用现有对象提供新创建的对象的 proto
Object.defineproperty() : 在对象上定一个新属性或者修改对象现有属性(不能被重写 writable)
Object.defineproperties() : 在一个对象上定义新的属性或修改现有属性
Object.entries() : 返回一个给定对象自身可枚举属性的键值对数组
Object.is() : 判断两个值是否为同一个
Object.key() : 返回一个给定对象的自身可枚举属性组成的数组
Object.values() : 返回一个给定对象的所有可枚举属性值的数组
Object.getOwnPropertyDescriptor() : 返回指定对象上一个自有属性对应的属性描述符。(自有属性指的是直接赋予该对象的属性,不需要从原型链上查找的属性)
Object.getProtopertyOf() : 返回指定对象的内部原型
link 和 import 的区别
- 从属关系区别:@import 是 CSS 提供的语法规则,只有导入样式的作用;link 是 HTML 提供的标签,不仅可以加载 CSS 文件,还可以定义 RSS、rel 连接属性等;
- 加载顺序区别:加载页面时,link 标签引入 CSS 被同时加载;@import 引入的 CSS 将在页面加载完毕后被加载
- 兼容性区别:@import 是 CSS2.1 才有的语法,故只可在 IE5+ 才能识别;link 作为 HTML 元素,不存在兼容性问题;
- Dom 可控性区别:可以通过 JS 操作 DOM ,插入 link 标签来改变样式;由于 DOM 方法是基于文档的,无法使用 @import 方式插入样式