记录一些突然想起来/容易混淆/难记的知识点
箭头函数和普通函数的区别
参考:ES6扩展 函数扩展 箭头函数”箭头函数和普通函数的区别”
- this指向父级作用域的this。
- 不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。(可以拿到event函数)
- 没有arguments对象,如要使用可在箭头函数中用剩余参数
...
将传入的参数聚合成数组:1
2
3
4
5
6
7
8
9
10
11// 箭头函数中调用arguments报错
const arg=()=>{
console.log(arguments);
}
arg(1,2,3);//报错:arguments is not defined at arg
// 可以使用 剩余参数运算符 代替arguments去拿传入的参数
const arg=(...args)=>{
console.log(args);
}
arg(1,2,3);//[1, 2, 3]
箭头函数作用
- 参考:ES6扩展 函数扩展 箭头函数”回调函数中的this”
- 解决问题:回调函数中的this默认是指向windows
- 解决方法:
存储
- 参考JS Web API 存储”localStorage和sessionStorage”
- 注意如何使用sessionStorage和LoacalStorage:setItem getItem
cookie有效期
已实验过cookie如果不设置过期时间,浏览器关闭就会消失。
浏览器缓存 与 http缓存
- 参考前端基础-HTTP HTML 浏览器(2)”说一下浏览器缓存”
- 注意:http缓存也是在客户端缓存的
- 前端缓存:
- 浏览器缓存:
- Cookie
- SessionStorage
- LoacalStorage
- http缓存:
- 强缓存:
- Cache-Control:相对时间
- Expires:基于服务器端的绝对时间
- 协商缓存:
- Last-Modified(响应头)与If-Modified-Since(请求头):资源的最后修改时间
- ETag(响应头)与If-None-Match(请求头):资源的唯一标识(一个字符串,类似指纹),由内容生成
- 强缓存:
- 浏览器缓存:
const定义引用类型 属性(值)可变
- const作为常量,如果定义一个对象,她的属性值可变吗?
- 可变,且可增加属性,不可变的只是对象的指向地址。(可参考let命令与const命令“常量为引用类型的时候可以修改该引用类型”)
- 如需冻结常量对象使其属性与属性值不能被修改可使用
Object.freeze(常量名)
JS基础知识面试题(1)中
手写简易jquery+手写原型链继承(html是jquery的方法吧?)都要注意使用this
for-in与for-of
- in 运算符:
- 如果指定的 属性 在指定的对象或其原型链中,则in 运算符返回true。
- 如果指定的 数组索引 在数组中,则in 运算符返回true。
for-in
与for-of
可参考《ES6扩展 对象扩展》
浅拷贝与深拷贝
- 不使用for-of的原因: 数组元素/对象属性为不可迭代的对象时会报错。
- 可参考JS浅拷贝与深拷贝
- 浅拷贝与深拷贝:
- 数组/对象 通用深拷贝:
- JSON.parse(JSON.stringify())缺点:无法实现对 数组/对象中 方法 的深拷贝,会显示为undefined
- 递归拷贝:参考“JS基础知识面试题(1)”
- 函数库lodash中的
_.cloneDeep()
,参考
Promise的then函数返回值
- 可参考JS异步进阶“then和catch如何影响状态的变化”
- Promise的then函数中如果返回普通对象会被自动转为Promise对象吗?(问题来源于JS基础知识面试题(2)”手写Promise加载一张图片”)
- 会
1
2
3
4const p = Promise.resolve().then(() => {
return 100;
})
setTimeout(() => console.log('p', p)) // p Promise {<fulfilled>: 100}
字符串、数组的常用方法【待记忆】
- JS对象之string、JS对象之数组(1)、JS对象之数组(2)
- 字符串和数组都有的方法:
- 位置查找(注意参数不同):
- 字符串:
- 数组:
- 截取:(不会改变原始字符串/数组)** slice(start,end)截取字符串/部分元素**
- 字符串截取:(不会改变原始字符串)
- substring(start,end)与slice的区别:
- 当参数为负数时,自动将参数转换为0
- substring()会将较小的数作为开始位置,将较大的数作为结束位置
- substr(start,len)注意:len可选,表示截取的字符总数,省略时截取至字符串的未尾(包含末尾).
- substring(start,end)与slice的区别:
- 位置查找(注意参数不同):
- 转换:
- 字符串=》数组: stringObject.split(分隔符)
- 数组=》字符串: arrayObject.join(分隔符 默认为逗号)
- 替换:
- 字符串:(不会影响原始字符串) stringObject.replace(替换前字符(串),替换后字符(串))
- 数组(删除、插入、替换都可):【会改变原始数组】 arrayObject.splice(index,count)原数组会变成被删除/插入/替换以后的数组。返回含有删除部分的元素的数组
字符串独有的
- 不会改变原始字符串: toUpperCase()转大写、toLowerCase()转小写、trim()删除头尾空格
数组独有的
- 直接修改原始数组:
- 返回值是数组新长度: push():在尾部添加元素、unshift():在开头添加元素
- 返回值是被删除的元素: pop():删除最后一个元素、shift():删除第一个元素
- 返回值是修改后数组:reverse()颠倒顺序、sort()升序/降序排序
- 不修改原数组:
- 返回值是新数组: concat()连接两个或多个数组、map()遍历数组并进行操作、ES6新增的Array.prototype.filter()数组过滤方法、ES6新增的Array.from()用于从类数组对象(arguments)生成数组或者浅拷贝数组
- 返回undefined: forEach()遍历数组的每个元素
总结(是否修改原数组)
- 字符串的方法都不会改变原始字符串
- 数组中会改变原始数组的方法有:
- 返回含有删除部分的元素的数组,原数组会变成被删除/插入/替换以后的数组: arrayObject.splice(index,count)删除、插入、替换
- 返回值是数组新长度: push():在尾部添加元素、unshift():在开头添加元素
- 返回值是被删除的元素: pop():删除最后一个元素、shift():删除第一个元素
- 返回值是修改后数组:reverse()颠倒顺序、sort()升序/降序排序
基础知识面试题3中“异步队列依次执行”
- 参考 JS基础知识面试题(3)
- 思路:队列 先进先出,Promise then,封装函数
- 方法1: Promise.resolve() + arr.forEach + PromiseObj.then(fn)
- 方法2: async+for-of+await PromiseObj
预加载的实现方法
- 懒加载 可参考 JS基础知识面试题(3)、预加载 慕课、预加载 实现方法
- 预加载(异步加载):提前加载图片,当用户需要查看时可直接从本地缓存中渲染。
- 例子: loading页面显示时就是正在提前加载众多图片的时候,等待切换到下一个页面时众多图片已经加载完毕。
- 实现方法:
- 显示loading页面时,将需要加载的图片src放入数组中
- 图片的load和error事件函数中就可以放进度条的进度计算,遍历图片数组加载图片(赋值给image对象的src)(error中也是正常进度,不然error就卡住永远无法加载结束)
- 等图片数组遍历加载结束,隐藏loading页,正常显示网页,此时所有图片已加载过一次,用户查看时会直接从本地读取
JS Web API 中
通用的事件监听函数(事件代理)
可以同时监听 普通绑定 和 代理绑定 的通用事件监听函数:普通绑定时this就指向触发事件的DOM元素;事件代理时就需要事件对象.target
来获取触发事件的DOM元素
手写ajax
模拟404情况,换成不存在的地址:
JS Web API AJAX中
- 同源和跨域可以看这篇帖子,写的非常简洁易懂
同源
为什么要有同源与不同源?
- 出于安全考虑,浏览器不允许,页面向不同源的接口请求数据,因为如果 接口 和 网页不同源,浏览器认为是2个不同的 服务器
- 不同的服务器中内容是不可控的,不允许访问了
总结说人话: 浏览器为了保护你的电脑安全
举个栗子: 你去肯德基店里点餐,店员只允许你点肯德基的产品(炸鸡,可乐,上校鸡块),如果此时你在肯德基店里面点麦当劳的产品,浏览器会认为你是坏人,就会让保安把你赶出去
- 同源策略:ajax请求时,浏览器要求当前网页和server必须同源(为了安全)
- 注意:同源是浏览器规定的,在服务端是可以发起跨域攻击的,比如爬虫,因为server端并无同源策略
- 同源:协议、域名、端口,三者必须一致
- 比如:前端
http://a.com:8080/
;server:https://b.com/api/xxx
(默认443端口)那么这前后端是三者都不一致
- 比如:前端
- 同源策略限制了跨域时的以下行为:
- Cookie、LocalStorage、sessionStorage 和 IndexDB 跨域无法读取,只有同源才能获取他们
- DOM 和 JS 对象跨域无法获取
- Ajax请求跨域时无效,可发送,但浏览器会拒绝接受响应
跨域的使用场景
- 跨域,简单来说就是浏览器使用 ajax请求 时,如果请求的 接口地址 和 当前打开的页面 地址 不同源
- 跨域问题 是浏览器对于ajax请求的一种安全限制:一个页面发起的ajax请求,只能是于当前页同源的路径,这能有效的阻止跨站攻击。
- 出现跨域问题的前提:
- ajax: 浏览器只有使用ajax发送请求才会出现跨域,href属性与src属性不会出现跨域
- 接口地址: ajax请求的url
- 打开的页面:当前页面的window.location.href
- 不同源: 浏览器使用ajax,向不同源的接口发送请求,称之为 跨域访问
- ajax: 浏览器只有使用ajax发送请求才会出现跨域,href属性与src属性不会出现跨域
- 注意:
- 所有的跨域,都必须经过server端允许和配合(包括前端jsonp)
- 未经server端允许就实现跨域,说明浏览器有漏洞,危险信号
- 具体配合可参考[《JS Web API AJAX》]
- 加载img CSS js可无视同源策略:
- 开发环境中,前后端联调时 使用nginx反向代理 或 proxy反向代理 根据url派发不同的端口,把跨域变为不跨域,使Ajax请求变成同源的从而传递数据
- 例子1:假设前端页面是3000端口,后端页面是8000端口。那么用户打开页面时打开的就是3000端口的页面,前端页面中使用ajax请求获取数据时就会去获取3000端口下的对应路径的数据。而后端页面是8000端口,所以数据其实保存在8000端口下的对应路径中。这就无法获取数据。此时使用nginx进行反向代理,遇到/api/开头的url时就分配到8000端口,遇到/开头的url时就分配到3000端口,这样就没问题了。
- 例子2:
- 开发环境中,假设前端服务是
http://localhost:8089/
,后端接口是存在http://test01.7debao.com/
服务上的 - 那么前端打开页面时打开的就是
http://localhost:8089/
,前端页面中使用ajax请求获取数据时就会去获取http://localhost:8089/
下的对应路径的数据 - 而后端接口是存在
http://test01.7debao.com/
服务上的,所以数据其实保存在http://test01.7debao.com/
下的对应路径中,这就导致无法获取数据 - 此时使用proxy进行反向代理,结合axios配置给接口加d baseUrl
/api
,遇到以/api
开头的URL请求会被代理分配到后端服务,其他以/
开头的URL请求会被前端服务处理显示页面或返回404错误 - 注意: 生产和测试环境下,我开发的项目前后端代码应该是部署在同一域名下,接口请求不存在跨域问题,所以不需要设置反向代理,具体是运维在云服务上配的,我只能看到没有处理接口,连axios的baseUrl都没设置,看起来是同源直接请求
- 开发环境中,假设前端服务是
跨域的实现方式
- 注意:如果是协议和端口造成的跨域问题,“前端”无法解决。
- 前提:所有的跨域,都必须经过server端允许和配合(包括前端jsonp)
- 对于跨域请求,一般首选使用CORS(跨域资源共享),因为它是一种标准、通用且相对简单的解决方案(jsonp就复杂多了),适用于大多数跨域问题。只有在特定情况下才需要考虑使用反向代理
- 可通过jsonp、CORS、反向代理实现跨域
- jsonp原理:可参考博客JS Web API AJAX
曾经的跨域杀手,专治各种跨域问题。现在慢慢的淡出历史舞台
- 由于存在一些安全风险和功能上的限制,以及现代浏览器支持其他更强大、更安全的跨域解决方案,JSONP的使用已经减少
- CORS(纯服务端):可参考博客JS Web API AJAX
目前的主流方案,也是最简单的方案。在实际工作中由后端人员来实现,前端不需要做任何事情
- WebSocket
- nginx反向代理:可参考博客项目登录(前端联调)和nginx反向代理
- proxy反向代理 : 可参考博客axios使用和简单封装
- jsonp原理:可参考博客JS Web API AJAX
- 如何选择跨域的实现方式可参考博客[JS Web API AJAX]
防抖 节流
- 防抖:一段时间内重复触发,以最后一次触发事件为基准进行计算,n秒内未触发才执行函数,在此之前的调用都会被忽略
- 监听一个输入框的文字变化后触发change事件,直接用keyup事件,则会频发触发change事件。如果使用防抖,则用户输入结束或暂停时,才会触发change事件。(防抖不仅限于change事件)
- 对某个按钮进行高频操作时,防止连续点击
- 例子
- 节流:以第一次触发事件为准,在指定时间内最多执行一次,多余的调用都会被忽略
- 拖拽一个元素时,要随时拿到该元素被拖拽的位置。这里直接用drag事件则会频发触发,很容易导致卡顿。而使用节流,则无论拖拽速度多快,都会每隔100ms触发一次(时间自定义)
- 注意:如果用防抖的话,就会出现卡顿的感觉,因为只在停止的时候执行了一次,这个时候就应该用节流,在一定时间内多次执行,会流畅很多(具体原因可见上方“防抖”中“防抖与节流的区别”)
- 轮询请求时也可用,比如批量处理大量数据时,后端需要长时间处理,前端需要动态展示处理结果,则可规定每500ms发送一次请求
- 如果不使用节流,在请求结束后再次调用请求一直到数据全部处理完成,则会出现连续疯狂请求的情况
- 例子
- 拖拽一个元素时,要随时拿到该元素被拖拽的位置。这里直接用drag事件则会频发触发,很容易导致卡顿。而使用节流,则无论拖拽速度多快,都会每隔100ms触发一次(时间自定义)
3种弹框
- 菜鸟教程
- 警告框:
alert("sometext");
- 确认框:
confirm("sometext");
1
2
3
4
5
6
7
8
9var r=confirm("按下按钮");
if (r==true)
{
x="你按下了\"确定\"按钮!";
}
else
{
x="你按下了\"取消\"按钮!";
} - 提示框:
prompt("sometext","defaultvalue");
当提示框出现后,用户需要输入某个值,然后点击确认或取消按钮才能继续操纵。- 如果用户点击确认,那么返回值为输入的值。
- 如果用户点击取消,那么返回值为 null。
1
2
3
4
5
6var person=prompt("请输入你的名字","Harry Potter");
if (person!=null && person!="")
{
x="你好 " + person + "! 今天感觉如何?";
document.getElementById("demo").innerHTML=x;
}
组件还是类?如何选择
- 封装组件的情况:
- 可重用性需求: 如果你的功能是可复用的,而且在应用程序的多个地方都可能被使用,封装成组件是一个很好的选择。Vue.js、React 和 Angular 等前端框架都鼓励组件化开发。
- UI 组件: 当你的功能包含一组相关的 UI 元素、样式和行为时,封装成组件可以更好地组织代码。这有助于提高代码的可维护性和可重用性。
- 独立性: 如果你的功能是独立的,有自己的状态、行为和样式,且不依赖于外部环境,那么封装成组件可以使它更加独立和可测试。
- 框架要求: 如果你使用的前端框架或库是组件化导向的,比如 Vue.js 或 React,那么封装成组件符合框架的设计理念,并能更好地利用框架的特性。
- 封装成类的情况:
- 面向对象编程: 如果你更喜欢使用面向对象的编程范式,或者项目中已经采用了这种方式,那么封装成类可能更符合你的编程风格。
- 服务类或工具类: 当你的功能更偏向于服务性质,例如提供一些工具方法、服务类,而不涉及 UI 层的展示时,封装成类可能更适合。
- 单例模式: 如果你的功能只需要一个实例,并且需要保持全局唯一性,封装成类可以更方便实现单例模式。
- 业务逻辑层: 在某些情况下,业务逻辑可能更适合使用类来组织,尤其是当逻辑涉及多个组件或模块时。
- 综合考虑时,你可能会在项目中同时使用组件和类,根据具体的场景选择最适合的方式。在现代前端开发中,组件化的思想更加流行,因为它有助于构建可维护和可扩展的应用程序。在大多数情况下,选择封装成组件更符合前端开发的潮流
工作场景
- 例子代码可见《vue 全局挂载自定义函数(class)》
- 封装class的场景:本身旺旺聊天图标就是个span元素,我还需要一个appkey属性传给span,两种不同点亮图标的方法以及前置方法,虽然我现在只用一个方法,但把其他情况的方法也放一起封装到类里,以便后续开发做其他操作
- 为何封装class:也可以封装组件,看个人习惯吧,如果封装组件,那么span元素用于显示,appkey在外部获取后存在vuex中(不然多个页面使用,一个个获取传入太麻烦了),在合适时机通过$refs调用图标点亮的方法