目录
1、缓存
缓存的优点
- 减少不必要的数据传输,减少带宽
- 减少服务器负担,提升网站性能
- 加快客户端页面加载的速度
- 用户体验友好
缺点:客户端与服务端时间不一致,资源更新会导致客户获取信息滞后
前端缓存分为HTTP缓存和浏览器缓存
HTTP缓存又分为强缓存(本地缓存)和协商缓存
参考:https://blog.csdn.net/weixin_46587293/article/details/123342557
当浏览器第一次发起请求时(请求头中没有If-Modified-Since)server会在响应中告诉浏览器这个资源最后修改的时间(响应头中的Last-Modified)。当你再次请求这个资源时(在请求头中包含字段 If-Modify-Since),浏览器会询问server这个资源有没有被修改(请求头中If-Modified-Since):
- 若资源未过期,则返回 304 表示资源未修改
- 若资源已过期,则返回 200 并附上最新资源以及新的 last-modified
2、观察者模式VS发布订阅模式
发布订阅是一种消息范式,消息的发送者(发布者)不会将消息直接发送给消息的接收者(订阅者),而是将消息分为不同的类别。无需知道哪些订阅者的存在;同样订阅者也可以表达对一个或多个类别的兴趣,只接受感兴趣的信息,无需了解哪些发布者的存在
订阅者(Subscriber)把自己想订阅的事件注册(Subscribe)到调度中心(Event Channel),当发布者(Publisher)发布该事件(Publish Event)到调度中心,也就是该事件触发时,由调度中心统一调度(Fire Event)订阅者注册到调度中心的处理代码。
//事件触发器
class EventEmitter(){
constructor(){
// 初始化对象{ 'click':[fn1,fn2],'change':[fn] }
this.subs=Object.create(null)
}
//注册事件
$on(eventType,handler){
this.subs[eventType] = this.subs[eventType] || []
this.subs[eventType].push(handler)
}
//触发事件
$emit(eventType){
if(this.subs[eventType]){
this.subs[eventType].forEach(handler => {
handler()
})
}
}
}
//测试
let em =new EventEmitter()
em.$on('click',()=>{
console.log('click1')
})
em.$on('click',()=>{
console.log('click2')
})
em.$emit('click') //打印结果 click1,click2
- 观察者模式
观察者模式定义了对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知,并自动更新。
观察者模式属于行为性模式,行为型模式关注的是对象间的通讯,观察者模式就是观察者与被观察者之间的通讯
观察者(Observer)直接订阅(Subscribe)主题(Subject),而当主题被激活的时候,会触发(Fire Event)观察者里的事件。
观察者 --Watcher
update():当事件发生时,具体要做的事情。
发布者 --Dep
subs数组:存储所有的观察者
addSub():添加观察者
notify():当事件发生,调用所有观察者的update()方法
//发布者-目标
class Dep{
constructor() {
//记录所有的订阅者
this.subs=[]
}
//添加订阅者
addsub(sub){
if(sub && sub.update){
this.subs.push(sub)
}
}
//发布通知
notify(){
this.subs.forEach(sub=>{
sub.update()
})
}
}
//订阅者-观察者
class Watcher{
update(){
console.log('update')
}
}
//测试
let dep=new Dep()
let watcher=new Watcher()
dep.addsub(watcher)
dep.notify() //打印结果 update
- 观察者模式与发布订阅的区别
- 观察者模式和订阅模式的区别是没有调度中心,只有发布者和订阅者,并且发布者需要知道订阅者的存在
- 在发布订阅模式中,组件是松散耦合的,正好与观察者模式相反
- 观察者模式大多数时候是同步的,比如当事件触发,Subject 就会去调用观察者的方法。而发布-订阅模式大多数时候是异步的(使用消息队列)
- 观察者模式需要在单个应用程序地址空间中实现,而发布-订阅更像交叉应用模式。
3、防抖和节流
防抖和节流都是防止某一事件频繁触发
防抖是指在指定的单位时间内,如果重复触发了相同的事件,则取消上一次的事件,重新开始计时;将多次执行变为只执行一次
典型场景:搜索框搜索输入
<body>
<input id="int" type="text">
</body>
<script>
const int = document.getElementById("int")
// 调接口的函数
const getData = function () {
console.log("调接口了");
}
int.addEventListener('input', double(getData, 1000))
// 封装防抖函数
function double(fn, time) {
let timeout
return () => {
// 先清除掉上一次的计时器
clearTimeout(timeout)
timeout = setTimeout(() => {
fn()
}, time)
}
}
</script>
lodash插件解决防抖
<html lang="en">
<body>
<p>
请你输入你搜索的内容:<input type="text">
</p>
</body>
</html>
<script>
let input=document.querySelector('input')
// 文本发生变化立即执行
/* input.oninput = function(){
console.log("ajax请求");
} */
// 用户输入后1s发送请求
input.oninput = _.debounce(function(){
console.log("ajax发送请求");
},1000)
// lodash插件:里面封装了函数的防抖与节流的业务【闭包+延迟器】
// 下载:npm i --save lodash
// 1.lodash数据库对外暴露_函数
/* console.log(_);
_.debounce(function(){
console.log('1秒之后执行一次');
},1000)() */
</script>
节流:单位时间内,频繁出发事件,只执行一次
典型场景:高频事件(快速点击、鼠标滑动、resize事件、scroll事件)
<body>
<input id="int" type="text">
</body>
<script>
const int = document.getElementById("int")
// 调接口的函数
const getData = function () {
console.log("调接口了");
}
int.addEventListener('input', double(getData, 1000))
// 封装节流函数
function double(fn, time) {
let isLoad = false
return () => {
if (!isLoad) {
// 设置为true后,即禁止下一个计时器执行
isLoad = true
setTimeout(() => {
fn()
// 等这个计时器执行完毕后,再设置为false,才允许下一个计时器执行
isLoad = false
}, time)
}
}
}
</script>
lodash插件解决节流
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="../node_modules/lodash/lodash.js"></script>
</head>
<body>
<div>
<h1>我是计数器<span>0</span></h1>
<button>点我加一</button>
</div>
</body>
</html>
<script>
// 获取节点
let span = document.querySelector('span')
let button = document.querySelector('button')
let count = 0
// 计数器在一秒内数字只能加一
/* button.onclick=function(){
count++;
span.innerHTML = count
} */
button.onclick=_.throttle(function(){
// 节流:这个回调函数1s执行一次
count++;
span.innerHTML = count
},1000)
</script>
实现代码防抖与节流的原理_防抖节流原理_小智玩前端的博客-CSDN博客
4、闭包
闭包是指有权访问另一个函数作用域中的变量的函数;使用闭包主要是为了设计私有的方法和变量;
闭包的特性
- 函数嵌套函数
- 可以让函数的外部访问到函数内部的局部变量
- 参数和变量不会以垃圾回收机制回收
优点:可以避免使用全局变量,防止全局变量污染
缺点:可能造成内存泄露或溢出
因为js有自动的垃圾回收机制,当变量不被引用时就会通过特殊的算法回收(一般是标记清除和引用计数两种方法),当一个函数执行完后作用域会被销毁,而闭包可以访问父级作用域,父级函数中的变量在闭包中被引用,那么就不会被垃圾回收;所以闭包可以实现在函数外部访问内部的局部变量,并且可以使让这些变量的值始终保持在内存中。
var a=10
function fun() {
console.log(a);
}
function foo(fun){
var a = 20;
(function(){
fun()
})()
}
foo(fun)//10
function fun() {
let a = 1
function foo(){
return ++a
}
return foo
}
var b = fun()
console.log(b());//2
console.log(b());//3 闭包中的变量不会释放
5、可枚举属性
Object.defineProperty()方法中enumerable配置项
可枚举的属性可以通过for...in循环进行遍历(除非该属性名是一个Symbol),或者通过Object.keys()方法返回一个可枚举属性的数组。
js中的基本包装类型的原型属性是不可枚举的,比如Object,Array,Number等
function disney(name,sex){
this.name=name
this.sex=sex
}
let Qibao1=new disney("Dafu","male")
Object.defineProperty(Qibao1, "age", {
value: 18,
//是否为枚举属性
enumerable: false
});
for (p in Qibao1){
console.log(p); //name sex
}
let Qibao2=new disney("Linabell","female")
Object.defineProperty(Qibao2, "age", {
value: 6,
enumerable: true
});
console.log("########");
for (q in Qibao2){
console.log(q); //name sex age
}
6、Object.assign
用于将所有可枚举属性的值从一个或多个源对象复制到目标对象,且返回目标对象。
1)属性重名时,源对象中的属性值覆盖目标对象中的属性值
2)源对象可以是多个
作用:Object.assign可以实现对象的合并。
语法:Object.assign(target, ...sources)
let Qibao1=new disney("Dafu","male")
let Qibao2=new disney("Linabell","female")
Object.defineProperty(Qibao2, "age", {
value: 6,
enumerable: false
});
console.log(Object.assign(Qibao1,Qibao2));
控制台输出:
let Qibao1=new disney("Dafu","male")
let Qibao2=new disney("Linabell","female")
Object.defineProperty(Qibao2, "age", {
value: 6,
enumerable: true
});
console.log(Object.assign(Qibao1,Qibao2));
控制台输出:
将两个或多个对象的属性合并到一起,不改变原有对象的属性,可以用一个空的对象作为target对象。
console.log(Object.assign({},Qibao1,Qibao2));
控制台输出:
7 、数组拍平
多维数组扩展为一维数组并返回
1.Array.prototype.flat() (flat方法返回的是新数组)
const arr = ["????", ["????", "????"], ["????", ["????", ["????"]], "????"]];
// 不传参数时,默认“拉平”一层
arr.flat();
// ["????", "????", "????", "????", ["????", ["????"]], "????"]
// 传入一个整数参数,整数即“拉平”的层数
arr.flat(2);
// ["????", "????", "????", "????", "????", ["????"], "????"]
// Infinity 关键字作为参数时,无论多少层嵌套,都会转为一维数组
arr.flat(Infinity);
// ["????", "????", "????", "????", "????", "????", "????"]
// 传入 <=0 的整数将返回原数组,不“拉平”
arr.flat(0);
arr.flat(-10);
// ["????", ["????", "????"], ["????", ["????", ["????"]], "????"]];
// 如果原数组有空位,flat()方法会跳过空位。
["????", "????", "????", "????",,].flat();
// ["????", "????", "????", "????"]
2.展开运算符+arr.some()
function flatter(arr){
while(arr.some(item=>Array.isArray(item))){
arr = [].concat(...arr)
}
return arr
}
arr = [1,2,3,[4,5,[6,7]],8,9]
console.log(flatter(arr));//[1,2,3,4,5,6,7,8,9]
3.toString+str.split
function flatter(arr){
return arr.toString().split(',').map(item=>{
return +item //转换为数字类型
})
}
arr = [1,2,3,[4,5,[6,7]],8,9]
console.log(flatter(arr))
4.递归
function flatter(arr){
let res = []
arr.forEach(item => {
if(item instanceof Array){
res = res.concat(flatter(item))
}else{
res.push(item)
}
});
return res
}
arr = [1,2,3,[4,5,[6,7]],8,9]
console.log(flatter(arr))
5.arr.reduce+递归
reduce方法:arr.reduce(callback, initialValue)
reduce为数组中的每个元素依次执行回调函数,接受4个参数:初始值(或者上次回调函数的返回值)、当前元素值、当前索引、调用reduce的数组。
initialValue为第一个参数的初始值,未设置初始值时默认为数组的第一个元素
1)reduce实现数组累加/累乘
arr = [2,34,2.2,34,3]
console.log(arr.reduce((pre,cur)=>pre+cur));
2)reduce找出数组中的最大/最小值
arr = [2,34,2.2,34,3,2,3]
console.log(arr.reduce((pre,cur)=> pre>cur?pre:cur));//最大值
console.log(arr.reduce((pre,cur)=> pre<cur?pre:cur));//最小值
3)reduce实现数组元素个数统计
arr = [2,34,2.2,34,3,2,3]
console.log(arr.reduce((pre,cur)=>{
pre[cur] = pre[cur]+1 || 1
return pre
},{}));//{2: 2, 3: 2, 34: 2, 2.2: 1}
4)reduce实现数组去重
arr = [2,34,2.2,34,3,2,3]
console.log(arr.reduce((pre,cur)=> {
if(!pre.includes(cur)){
pre.push(cur)
}
return pre
},[]));
5)reduce实现数组拍平
function flatter(arr){
return arr.reduce((pre,cur)=>pre.concat(cur instanceof Array ? flatter(cur):cur),[])
}
arr = [1,2,3,[4,5,[6,7]],8,9]
console.log(flatter(arr))
8、把平铺的数组结构转成树形结构
第一种
1) 给数组的每个元素都加上children用于存储二级数组
创建一个对象map,元素id为key,元素本身为value;便于对应关系
2)判断每个元素的pid:若pid不存在,说明为一级数组,直接push进最终输出数组
若pid存在,则为二级数组,push进pid对应的children属性中
(由于数组中的元素和map中都为引用类型,所以操作map中的元素数组中的元素也会发生变化)
<script>
/**
* 把平铺的数组结构转成树形结构
*/
const arr = [
{ 'id': '29', 'pid': '', 'name': '总裁办' },
{ 'id': '2c', 'pid': '', 'name': '财务部' },
{ 'id': '2d', 'pid': '2c', 'name': '财务核算部'},
{ 'id': '2f', 'pid': '2c', 'name': '薪资管理部'},
{ 'id': 'd2', 'pid': '', 'name': '技术部'},
{ 'id': 'd3', 'pid': 'd2', 'name': 'Java研发部'}
]
function tranListToTreeData(list){
let treeData = [],map = {};
list.forEach(item => {
if(!item.children){
item.children = []
}
map[item.id] = item
});
list.forEach(item=>{
if(item.pid){
map[item.pid].children.push(item)
}else{
treeData.push(item)
}
})
return treeData;
}
let res = tranListToTreeData(arr)
console.log(res);
// 因为都是引用数据类型,所以原数组中的值也会发生变化
</script>
输出结果:
第二种:递归
先筛选出一级数组的元素
再筛选pid与一级数组id相同的元素加入该一级数组的children属性
function findChildren(list,id){
let treeList = []
treeList = list.filter(item=>item.pid===id)
treeList.forEach(item => {
item.children = findChildren(list,item.id)
});
return treeList
}
let res = findChildren(arr,'')
console.log(res);
9、arr.forEach()与arr.map()
forEach() 针对每一个元素执行提供的函数;返回结果为undefined 不能中断(break和return) ;修改原来的数组
map() 创建一个新的数组,其中每一个元素由调用数组中的每一个元素执行提供的函数的来;返回结果为修改后的新数组 map()的执行速度优于forEach()
callback函数都接收当前元素item,当前元素下标index,数组本身arr
arr.forEach((item,index,arr)=>{})
l两种方法都不能用break中断,否则会引发异常;
map和forEach抛出throw new error()通过try catch去捕获这个错误来终止循环
let list=[1,2,3,4,5,6];
try{
list.map(item=>{
if(item===3){
throw new Error()
}
console.log(item)
})
} catch {}
// 1 2
10、轮播图
1)DOM实现
将图片的url存入数组 开启定时器
var img1 = document.getElementById("img1");
//创建一个数组来保存图片的路径
var imgArr = ["../img/甜甜/2013/2013.jpg","../img/甜甜/2013/2013-1.jpg",……];
//创建变量保存当前图片的索引
var index = 0;
//定义变量 保存定时器标识
var timer;
var btn01 = document.getElementById("btn01");
btn01.onclick = function(){
/*
* 目前 每点击一次按钮就会开启一个定时器
* 点击多次就会开启多个定时器,导致图片的切换速度过快
* 并且我们只能关闭最后一次开启的定时器
*/
//在开启定时器前,需要将当前元素上的其他定时器关闭
clearInterval(timer);
/*
* 开启定时器 自动切换图片
*/
timer = setInterval(function(){
//使索引自增
index++;
//判断索引是否超过最大索引
index %= imgArr.length;
//修改img1的src属性
img1.src = imgArr[index];
},1000);
};
2)swiper插件
swiper插件使用要保证页面中数据和结构都完整
swiper-->slide grid(网格分布)-->slidesPerView 设置轮播图一次展示多少张图片
<div class="swiper-container" id="floor1Swiper" ref="cur">
<div class="swiper-wrapper">
<div class="swiper-slide" v-for="carousel in list" :key="carousel.id">
<img :src="carousel.imgUrl" />
</div>
</div>
<!-- 如果需要分页器 -->
<div class="swiper-pagination"></div>
<!-- 如果需要导航按钮 -->
<div class="swiper-button-prev"></div>
<div class="swiper-button-next"></div>
</div>
如果在mounted中使用,数据请求已经完成,但不能保证v-for循环完成,即结构不一定完整
解决方案一:结合延时器
setTimeout(() => {
var mySwiper = new Swiper(this.$refs.cur, {
// direction: 'vertical', // 垂直切换选项 默认为水平方向
loop: true, // 循环模式选项
// 如果需要分页器
pagination: {
el: '.swiper-pagination',
clickable :true,//点击小球(分页器)也能切换图片
},
// 如果需要前进后退按钮
navigation: {
nextEl: '.swiper-button-next',
prevEl: '.swiper-button-prev',
},
},2000)
})
解决方案二: nextTick(DOM节点更新后执行回调)
<script>
import Swiper from 'swiper';
export default {
name: 'Carousel',
props:['list'],
watch: {
list: {
immediate: true, //立即监听 无论数据有无变化 先监听一次 否则监听不到list的变化 因为list由父亲传输 不会发生变化
handler() {
// 只能监听到数据已经有了 但v-for动态渲染的情况不能确定
this.$nextTick(() => {
var mySwiper = new Swiper(this.$refs.cur, {
// direction: 'vertical', // 垂直切换选项 默认为水平方向
loop: true, // 循环模式选项
// 如果需要分页器
pagination: {
el: '.swiper-pagination',
clickable: true,//点击小球(分页器)也能切换图片
},
// 如果需要前进后退按钮
navigation: {
nextEl: '.swiper-button-next',
prevEl: '.swiper-button-prev',
}
})
})
}
}
}
}
11、数据类型判断
1)typeof
返回表示数据类型的字符串,返回结果包括:number、string、boolean、object、undefined、function
null/数组/实例对象:返回object
JS中小数计算精度问题:细说JavaScript中小数点计算不精准的原因和解决方案
2)instanceOf
A instanceOf B 判断A是否为B的实例
用来判断两个对象是否属于原型链关系,不能获取对象的具体类型
3)constructor
当一个函数F被定义时,JS引擎会为F添加prototype原型,在prototype上添加constructor属性,并让其指向F的引用;当执行var f = new F()时,F被当作构造函数,F原型的constructor传递到f身上 f.constructor == F
问题:null和undefined是无效对象,不会有constructor
当开发者重写prototype后,原有的constructor消失,默认为Object
4)Object.prototype.toString()
let num = 123
console.log(num.toString());//123
console.log(Object.prototype.toString.call(num));//[object Number]
console.log(num.toString.call(num));//123
必须通过Object.prototype.toString.call来获取,而不能直接 obj.toString()。因为大部分的对象都实现了自身的toString方法,这样就可能会导致Object的toString被终止查找,因此要用call来强制执行Object的toString方法。
5)===
可以判断undefined和null
let a
console.log(a === undefined);//true 定义未赋值
let b = null
console.log(b === null);//true 定义且赋值为null
==:值相等(如果两边操作数类型不同,会先做隐式类型转换)
===:值和类型都相等
Object.is():+0和-0不等;NAN=NAN
console.log(-0 === +0);//true
console.log(-0 == +0);//true
console.log(Object.is(-0,+0));//false
console.log(NaN == 0/0);//false
console.log(NaN === 0/0);//false
console.log(Object.is(NaN,0/0));//true
12、事件循环(Event Loop)
- 线程和进程
进程:计算机中的程序关于某数据集合上的一次运行活动,是操作系统进行分配资源和调度的基本单位
线程:操作系统能够进行运算调度的最小单位
一个进程中可以并发多个线程,每条线程并行执行不同的任务
JavaScript语言是单线程的
浏览器这个进程中有许多线程,其中一个是JS引擎线程,还有一个GUI渲染线程;这两个线程是互斥的
JS引擎线程是基于事件循环来执行的 解决了JS中单线程阻塞问题
JavaScript代码的执行过程中,除了依靠函数调用栈来搞定函数的执行顺序外,还依靠任务队列来搞定另一些代码的执行,整个执行过程,我们称为事件循环过程
JS中所有任务可以分为两种:同步任务 异步任务
同步任务:在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务
异步任务:不进入主线程,而进入“任务队列”(task queue)的任务,只有“任务队列”通知主线程某个异步任务可以执行了,该任务才会进入主线程执行
异步任务又分为:宏任务(macro-task) 微任务(micro-task)
常见宏任务:script(整块代码) setInterval() setTimeout() setImmediate() ajax 事件绑定 I/O UI render
常见微任务:new Promise()后的then和catch函数(Promise本身是同步的) async/await new MutationObserver() process.nextTick(Nodejs)
若同时存在promise和nextTick,则先执行nextTick
任务队列中微任务和宏任务同时存在时,先执行微任务再执行宏任务;
setTimeout(() => {
console.log('异步1任务time1');
new Promise(function (resolve, reject) {
console.log('异步1宏任务promise');
setTimeout(() => {
console.log('异步1任务time2');
}, 0);
resolve();
}).then(function () {
console.log('异步1微任务then')
})
}, 0);
console.log('主线程宏任务1');
setTimeout(() => {
console.log('异步2任务time2');
}, 0);
new Promise(function (resolve, reject) {
console.log('宏任务promise');
// reject();
resolve();
}).then(function () {
console.log('微任务then')
}).catch(function () {
console.log('微任务catch')
})
console.log('主线程宏任务2');
输出:主线程宏任务1----宏任务promise----主线程宏任务2----微任务then----异步1任务time1----异步1宏任务promise----异步1微任务then----异步2任务time2----异步1任务time2
async/await执行顺序
async会将其后函数返回值封装为一个Promise对象,而await会等待这个Promise完成,并将其resolve的结果返回出来
async函数在遇到await后会立即返回Promise对象,继续执行async函数外部逻辑,async函数内部会被await阻塞
console.log('script start')
async function async1() {
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2 end')
}
async1()
setTimeout(function() {
console.log('setTimeout')
}, 0)
new Promise(resolve => {
console.log('Promise')
resolve()
})
.then(function() {
console.log('promise1')
})
.then(function() {
console.log('promise2')
})
console.log('script end')
输出:async2 end----Promise----script end----async1 end----promise1----promise2----setTimeout
13、let、var和const
1)var:使用var操作符定义的变量会变成包含它的函数的局部变量
变量提升:var关键字定义的变量会自动提升到函数作用域顶部
var可以重复声明
2) let、const:不存在变量提升
不可以重复声明
let/const声明的范围是块作用域,var声明的范围是函数作用域
let在全局作用域中声明的变量不会成为window的属性,var声明的变量则会
var num1=20
console.log(window.num1);//20
let num2=10
console.log(window.num2);//undefined
let和var的for循环
for (let i = 0; i < 10; i++) {
setTimeout(()=>{
console.log('let'+i);//0,1,2,3……9
},0)
}
for (var i = 0; i < 10; i++) {
setTimeout(()=>{
console.log('var'+i);//10,10,10……10
},0)
}
setTimeout是异步任务,每一次for循环时,setTimeout都执行一次,但里面的函数没有被执行,而是被放到任务队列中等待执行;只有主线上的任务执行完毕,才会执行任务队列中的任务,也就是等for循环执行完毕,才会执行setTimeout的回调函数
对于let:let不仅将i绑定到for循环中,它将i重新绑定到循环体的每一次迭代中,确保上一次迭代结束的值被重新赋值,即每次迭代过程中i都是独立的存在。setTimeout的回调函数属于一个新的作用域
对于var:此时i值已经变为10
解决var声明变量,连续输出相同数的问题:闭包
for (var i = 0; i < 10; i++) {
(function (j) {
setTimeout(function timer() {
console.log(j);
}, 0);
})(i);
}
通过闭包将i的变量驻留在内存中。
3) const:声明一个只读的常量,声明时必须初始化(赋值);一旦声明常量的值不能改变(栈中的值不能变;引用类型的内存地址不能变,值可以变)
let arr = []
const a = arr
arr[0]=1
console.log(a);//[1]
14、原型链
所有对象都有一个__proto__(隐式原型)属性 在创建对象时自动添加,默认值是构造函数的prototype对象
所有函数都有一个prototype(显式原型)属性 在定义函数时自动添加,默认值是一个空Object对象
原型链:
访问一个对象属性时:先在自身属性中查找,找到返回;如果没有,再沿着__proto__这条链向上查找,找到返回;如果最终没找到,返回undefined
Object的原型对象是原型链的尽头
console.log(Object.prototype.__proto__);//null
所有函数都是Function的实例
var F = function(){};
Object.prototype.a = function(){
console.log('a()')
};
Function.prototype.b = function(){
console.log('b()')
};
var f = new F();
f.a() //a()
f.b() //报错 F.prototype是个对象 所以其构造函数为Object
F.a() //a()
F.b() //b()
console.log(f instanceof Function);//false
console.log(F instanceof Function);//true
console.log(f instanceof Object);//true
console.log(F instanceof Object);//true
console.log(Object instanceof Function);//true
console.log(Function instanceof Object);//true
15、this指向情况
1)当以函数形式调用时,this指向window【test()】
2)当以方法形式调用时,this指向调用方法的对象【p.test()】
3)当以构造函数形式调用时,this指向新创建的对象【new test()】
4)使用call和apply调用时,this指向指定的对象【p.call(obj)】
16、BOM、DOM
JavaScript由三个部分组成:
EMCAScript:描述了JS的语法和基本对象
BOM(浏览器对象):与浏览器交互的方法和对象
DOM(文件对象模型):处理网页内容的方法和接口
1) BOM(Browser Object Model) 浏览器对象模型,BOM可以使我们通过JS来操作浏览器
在BOM中为我们提供了一组对象来完成对浏览器的操作
BOM对象:
* Window 代表的是整个浏览器窗口,同时window也是网页中的全局对象
* Navigator 代表当前浏览器的信息,通过该对象可以来识别不同的浏览器
* Location 代表当前浏览器的地址栏信息,通过Location可以获取地址栏信息或者操作浏览器跳转页面
* History 代表浏览器的历史记录,可以通过该对象来操作浏览器的历史记录;由于隐私的原因,该对象不能获取具体的历史记录,只能操作浏览器向前或向后;该操作只在档次访问时有效
* Screen 代表用户的屏幕信息,通过该对象可以获取到用户的显示器的相关的信息
2)DOM (Document Object Model)文档对象模型,JS通过DOM来对HTML文档进行操作
一个网页就是一个文档 将网页的每一部分都转换为一个对象
DOM是W3C的标准,所有浏览器共同遵守的标准
BOM是各个浏览器厂商根据DOM在各自浏览器上的实现(表现为不同浏览器定义有差别、实现方式不同)
window为BOM对象,而非JS对象
参考:DOM和BOM的区别
17、事件的传播
-W3C综合了两个公司的方案,将事件的传播分成了三个阶段
* 1.捕获阶段:从最外层的祖先元素向目标元素进行事件的捕获,默认此时不会触发事件
* 2.目标阶段:事件捕获到目标元素,开始在目标元素上触发事件
* 3.冒泡阶段:事件从目标元素向它的祖先元素传递,依次触发祖先上的元素
//取消冒泡:cancelBubble设置为true即可取消冒泡
*如果希望在捕获阶段就触发事件,可以将addEventListener()的第三个参数改为true(一般情况下不用
18、事件委托
利用事件冒泡,将子元素的事件都绑定到父元素身上
好处:1)替代循环绑定事件的操作,减少内存消耗,提高性能
2)简化了DOM节点更新时,相应事件的更新
缺点:1)事件委托基于冒泡,对于不冒泡的事件不支持
2)层级过多,冒泡过程中,可能会被某层阻止掉
3)理论上委托导致浏览器频繁调用处理函数,虽然可能不需要处理,所以建议就近委托
19、for...in和for...of
for...in保存键名 for...of保存键值
let arr = ['linabell','gelatoni','dafu']
for (const key in arr) {
console.log(key);//0 1 2
}
for (const value of arr) {
console.log(value);//linabell gelatoni dafu
}
20、Math.random()
<script>
console.log(Math.random());//生成[0,1)随机数
console.log(Matn.random()*10);//生成[0,10)随机数
</script>
Math.random()*(max-min)+min:生成[min,max)之间的随机数