互联网和浏览器
跨域
跨域是前端浏览器的概念,后端没有跨域概念。A前端给A后端发送请求一般是不会跨域,A前端给B后端发送请求一般是会跨域,A后端给B后端发送请求不会跨域。
-
JSONP
基本原理: 主要就是利用了
script
标签的src
没有跨域限制来完成的。执行过程:
- 前端定义一个解析函数(如:
jsonpCallback = function (res) {}
) - 通过
params
的形式包装script
标签的请求参数,并且声明执行函数(如cb=jsonpCallback
) - 后端获取到前端声明的执行函数(
jsonpCallback
),并以带上参数且调用执行函数的方式传递给前端 - 前端在
script
标签返回资源的时候就会去执行jsonpCallback
并通过回调函数的方式拿到数据了。
前端:
<script src='http://localhost:8080/api/jsonp?id=1&cb=jsonpCallback' type='text/javascript'></script>
后端将需要查询的数据及函数一起返回:
jsonpCallback({"title":"title1"})
前端在得到了后端返回的内容
jsonpCallback({"title":"title1"})
,发现里面是一段执行函数的语句,因此就会去执行这个jsonpCallback
方法了,并且是带了参数的。 - 前端定义一个解析函数(如:
-
CORS
在服务器中配置
//配置跨域 app.all('/*', function (req, res, next) { res.header('Access-Control-Allow-Origin', 'http://localhost') res.header("Access-Control-Allow-Credentials", true) //该字段可选。它的值是一个布尔值,表示是否允许发送Cookie res.header('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE') res.header('Access-Control-Allow-Headers', 'Content-Type,Content-Length, Authorization, Accept,X-Requested-With') next() })
-
反向代理(服务器代理)
a前端 发给 a后台 —> a后台 发给 b后台
成功后数据 原路返回
这就是反向代理。
sessionStorage和localStorage的区别 怎么设置过期时间
set(key, value, expired) {
/*
* set 存储方法
* @ param {String} key 键
* @ param {String} value 值,
* @ param {String} expired 过期时间,以分钟为单位,非必须
*/
let source = this.source;
source[key] = JSON.stringify(value);
if (expired){
source[`${key}__expires__`] = Date.now() + 1000*60*expired
};
return value;
}
get(key) {
/*
* get 获取方法
* @ param {String} key 键
* @ param {String} expired 存储时为非必须字段,所以有可能取不到,默认为 Date.now+1
*/
const source = this.source,
expired = source[`${key}__expires__`]||Date.now+1;
const now = Date.now();
if ( now >= expired ) {
this.remove(key);
return;
}
const value = source[key] ? JSON.parse(source[key]) : source[key];
return value;
}
remove(key) {
const data = this.source,
value = data[key]; //首席填坑官∙苏南的专栏
delete data[key];
delete data[`${key}__expires__`];
return value;
}
cookie属性
name字段为一个cookie的名称。
value字段为一个cookie的值。
domain字段为可以访问此cookie的域名
path字段为可以访问此cookie的页面路径。 比如domain是abc.com,path是/test,那么只有/test路径下的页面可以读取此cookie。
expires/Max-Age 字段为此cookie超时时间。若设置其值为一个时间,那么当到达此时间后,此cookie失效。不设置的话默认值是Session,意思是cookie会和session一起失效。当浏览器关闭(不是浏览器标签页,而是整个浏览器) 后,此cookie失效。
Size字段 此cookie大小。
http字段 cookie的httponly属性。若此属性为true,则只有在http请求头中会带有此cookie的信息,而不能通过document.cookie来访问此cookie。
secure 字段 设置是否只能通过https来传递此条cookie
secure是一个布尔值,它指定了在网络上如何传输cookie值。默认情况下,cookie是不安全的,也就是说,他们是通过一个普通的、不安全的http链接传输的。但是如果将cookie标记为安全的,那么它将只在浏览器和服务器通过https或其他安全协议链接是才被传输。
由于对第三方cookie(也就是那些和web页面中的图像相关而不是和web页面本身相关的cookie)的滥用,cookie对很多web用户来说声名很坏。例如,第三方cookie使用一个广告商公司从一个站点到另一个站点地跟踪用户,这种实际情况带来的隐私问题使得一些用户关闭了他们的cookie。在javascript代码中使用cookie之间,希望首先查看他们是否激活。在大多数浏览器中,可以通过检查navigator.cookieEnabled属性来做到这一点。
HTML
img标签中alt和title属性
alt
此属性的实质作用是图片在无法正确显示的时候起到文本替代的作用,不过在IE6下还起到了title的作用(鼠标放上去后的文字提示),IE的实现方法实际上是错误的。如果想在鼠标滑过时显示提示,应该用title属性。
title
鼠标滑过时显示的文字提示,用户体验上很重要。
<img src="图片路径" alt="logo" title="首页" />
CSS
常见的块级元素和行内块元素,以及它们有何不同
px em 和 rem的区别
怎么解决浮动中塌陷的问题
常见的布局方式
圣杯布局
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
body {
margin: 0;
height: 100%;
padding: 0;
}
.center, .left, .right{
height: 100%;
float: left;
background-color: #fff;
}
.center{
width: 100%;
background-color: red;
}
.left{
width: 100px;
margin-left: -100%;
background-color: green;
position: relative;
left: -100px;
}
.right{
width: 100px;
margin-left: -100px;
background-color: blue;
position: relative;
right: -100px;
}
.wrapper{
background: white !important;
padding: 0 100px;
}
.clearfix::after{
content: "";
display: block;
clear: both;
}
.wrapper,
.head,
.foot {
box-sizing: border-box;
width: 100%;
height: 150px;
background: yellow;
}
</style>
</head>
<body>
<div class="head">head</div>
<div class="clearfix wrapper">
<div class="center">center</div>
<div class="left">left</div>
<div class="right">right</div>
</div>
<div class="foot">foot</div>
</body>
</html>
双飞翼布局
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
body {
margin: 0;
height: 100%;
padding: 0;
}
.left,
.right {
width: 100px;
height: 100px;
float: left;
}
.center {
margin: 0 100px;
background-color: rgb(178, 42, 196);
}
.left {
margin-left: -100%;
background-color: green;
}
.right {
margin-left: -100px;
background-color: blue;
}
.wrapper {
width: 100%;
float: left;
}
.head,
.foot {
box-sizing: border-box;
width: 100%;
height: 150px;
background: yellow;
clear: both;
}
</style>
</head>
<body>
<div class="head">head</div>
<div class="wrapper">
<div class="center">center</div>
</div>
<div class="left">left</div>
<div class="right">right</div>
<div class="foot">foot</div>
</body>
</html>
自适应布局方法
CSS3新特性
flex怎么让三个子项目在横向上均等分
BFC
块级格式上下文,它是指一个独立的块级渲染区域,只有Block-level Box参与,该区域拥有一套渲染规则来约束块级盒子的布局,且与区域外部无关。
如何创建BFC
- float的值不是none
- position的值不是static或者relative
- display的值是inline-block、flex、inline-flex
- overflow:hidden
BFC的作用
可以取消盒子margin塌陷
可以阻止元素被浮动元素覆盖
CSS中百分比是相对于谁
用到百分比的属性:
-
width, height
相对于父元素
-
margin, padding
都相对于父元素的宽度,可以使用padding这个特性,实现正方形。
-
top, right, bottom, left
相对于父元素的高度或宽度
-
transform - translate
相对于自身元素的高度或宽度
-
background-position, background-size
background-position根据自身元素的宽高计算
background-size根据图片的大小进行计算. 需要注意的时, 当使用单个百分比(比如
background-size: 50%;
)计算时, height会隐式设为auto
, 当其height计算出来的值大于容器的高度时, 超出部分会隐藏. 如果需要全部显示, 需要明确设置宽和高的值(比如,background-size: 50% 50%;
)
水平居中和垂直居中
水平居中
-
若是行内元素, 给其父元素设置 text-align:center,即可实现行内元素水平居中.
-
若是块级元素, 该元素设置 margin:0 auto即可.
-
若子元素包含 float:left 属性, 为了让子元素水平居中, 则可让父元素宽度设置为fit-content,并且配合margin, 作如下设置:
.parent{ width: -moz-fit-content; width: -webkit-fit-content; width:fit-content; margin:0 auto; }
fit-content是CSS3中给width属性新加的一个属性值,它配合margin可以轻松实现水平居中, 目前只支持Chrome 和 Firefox浏览器.
-
使用flex 2012年版本布局, 可以轻松的实现水平居中, 子元素设置如下:
.son{ display: flex; justify-content: center; }
-
使用CSS3中新增的transform属性, 子元素设置如下:
.son{ position:absolute; left:50%; transform:translate(-50%,0); }
-
使用绝对定位方式, 以及负值的margin-left, 子元素设置如下:
.son{ position:absolute; width:固定; left:50%; margin-left:-0.5*自身宽度; }
垂直居中
单行文本
- 若元素是单行文本, 则可设置 line-height 等于父元素高度
行内块级元素
-
若元素是行内块级元素, 基本思想是使用display: inline-block, vertical-align: middle和一个伪元素让内容块处于容器中央.
.parent::after, .son{ display:inline-block; vertical-align:middle; //baseline、text-top、bottom 与父元素相比较 } .parent::after{ content:''; height:100%; }
这是一种很流行的方法, 也适应IE7.
元素高度不定
-
仿照行内块级元素写法
-
flex布局
父元素做如下设置即可保证子元素垂直居中:
.parent { display: flex; align-items: center; }
-
可用 transform , 设置父元素相对定位(position:relative), 子元素如下css样式:
.son{ position:absolute; top:50%; -webkit-transform: translate(0,-50%); -ms-transform: translate(0,-50%); transform: translate(0,-50%); }
元素高度固定
-
设置父元素相对定位(position:relative), 子元素如下css样式:
.son{ position:absolute; top:50%; height:固定; margin-top:-0.5*自身高度; }
优点
- 适用于所有浏览器.
缺点
- 父元素空间不够时, 子元素可能不可见(当浏览器窗口缩小时,滚动条不出现时).如果子元素设置了overflow:auto, 则高度不够时, 会出现滚动条.
-
设置父元素相对定位(position:relative), 子元素如下css样式:
.son{ position:absolute; height:固定; top:0; bottom:0; margin:auto 0; }
优点
- 简单
缺点
- 没有足够空间时, 子元素会被截断, 但不会有滚动条.
JS
ES6新特性
https://es6.ruanyifeng.com/#docs/string
- let 和 const 命令
- 变量的解构赋值
- 字符串的扩展
- 字符串的新增方法
- 正则的扩展
- 数值的扩展
- 函数的扩展
- 数组的扩展
- 对象的扩展
- 对象的新增方法
- Symbol
- Set 和 Map 数据结构
- Proxy
- Reflect
- Promise 对象
- Iterator 和 for…of 循环
- Generator 函数的语法
- Generator 函数的异步应用
- async 函数
- Class 的基本语法
- Class 的继承
- Module 的语法
- Module 的加载实现
- 编程风格
- 读懂规格
- 异步遍历器
- ArrayBuffer
- 最新提案
- Decorator
Null 和 undefined 的区别
null 不存在
undefined 还没来及赋值
在JavaScript中,null
和 undefined
几乎相等
在 if
语句中 null
和 undefined
都会转为false两者用相等运算符比较也是相等
null
表示没有对象,即该处不应该有值
1) 作为函数的参数,表示该函数的参数不是对象
2) 作为对象原型链的终点
undefined
表示缺少值,即此处应该有值,但没有定义
1)定义了形参,没有传实参,显示undefined
2)对象属性名不存在时,显示undefined
3)函数没有写返回值,即没有写return,拿到的是undefined
4)写了return,但没有赋值,拿到的是undefined
null和undefined转换成number数据类型
null
默认转成 0
undefined
默认转成 NaN
前端缓存的理解 或者 前端数据持久化的理解
https://zhuanlan.zhihu.com/p/44789005
强制缓存 (也叫强缓存)
强制缓存的含义是,当客户端请求后,会先访问缓存数据库看缓存是否存在。如果存在则直接返回;不存在则请求真的服务器,响应后再写入缓存数据库。
强制缓存直接减少请求数,是提升最大的缓存策略。 它的优化覆盖了文章开头提到过的请求数据的全部三个步骤。如果考虑使用缓存来优化网页性能的话,强制缓存应该是首先被考虑的。
可以造成强制缓存的字段是 Cache-control
和 Expires
对比缓存 (也叫协商缓存)
当强制缓存失效(超过规定时间)时,就需要使用对比缓存,由服务器决定缓存内容是否失效。
流程上说,浏览器先请求缓存数据库,返回一个缓存标识。之后浏览器拿这个标识和服务器通讯。如果缓存未失效,则返回 HTTP 状态码 304 表示继续使用,于是客户端继续使用缓存;如果失效,则返回新的数据和缓存规则,浏览器响应数据后,再把规则写入到缓存数据库。
对比缓存在请求数上和没有缓存是一致的,但如果是 304 的话,返回的仅仅是一个状态码而已,并没有实际的文件内容,因此 在响应体体积上的节省是它的优化点。它的优化覆盖了文章开头提到过的请求数据的三个步骤中的最后一个:“响应”。通过减少响应体体积,来缩短网络传输时间。所以和强制缓存相比提升幅度较小,但总比没有缓存好。
对比缓存是可以和强制缓存一起使用的,作为在强制缓存失效后的一种后备方案。实际项目中他们也的确经常一同出现。
对比缓存有 2 组字段(不是两个):
Last-Modified & If-Modified-Since
- 服务器通过
Last-Modified
字段告知客户端,资源最后一次被修改的时间,例如
Last-Modified: Mon, 10 Nov 2018 09:10:11 GMT
- 浏览器将这个值和内容一起记录在缓存数据库中。
- 下一次请求相同资源时时,浏览器从自己的缓存中找出“不确定是否过期的”缓存。因此在请求头中将上次的
Last-Modified
的值写入到请求头的If-Modified-Since
字段 - 服务器会将
If-Modified-Since
的值与Last-Modified
字段进行对比。如果相等,则表示未修改,响应 304;反之,则表示修改了,响应 200 状态码,并返回数据。
但是他还是有一定缺陷的:
- 如果资源更新的速度是秒以下单位,那么该缓存是不能被使用的,因为它的时间单位最低是秒。
- 如果文件是通过服务器动态生成的,那么该方法的更新时间永远是生成的时间,尽管文件可能没有变化,所以起不到缓存的作用。
Etag & If-None-Match
为了解决上述问题,出现了一组新的字段 Etag
和 If-None-Match
Etag
存储的是文件的特殊标识(一般都是 hash 生成的),服务器存储着文件的 Etag
字段。之后的流程和 Last-Modified
一致,只是 Last-Modified
字段和它所表示的更新时间改变成了 Etag
字段和它所表示的文件 hash,把 If-Modified-Since
变成了 If-None-Match
。服务器同样进行比较,命中返回 304, 不命中返回新资源和 200。
Etag 的优先级高于 Last-Modified
缓存小结
当浏览器要请求资源时
-
调用 Service Worker 的
fetch
事件响应 -
查看 memory cache
-
查看 disk cache。这里又细分:
-
-
如果有强制缓存且未失效,则使用强制缓存,不请求服务器。这时的状态码全部是 200
-
如果有强制缓存但已失效,使用对比缓存,比较后确定 304 还是 200
-
-
发送网络请求,等待网络响应
-
把响应内容存入 disk cache (如果 HTTP 头信息配置可以存的话)
-
把响应内容 的引用 存入 memory cache (无视 HTTP 头信息的配置)
-
把响应内容存入 Service Worker 的 Cache Storage (如果 Service Worker 的脚本调用了
cache.put()
)
Require 和 import
import静态编译,import的地址不能通过计算
require就可以,例如 const url = “a” + “b”;
Import url 直接报错了
require(url)不会报错
所以require都会用在动态加载的时候
var let const 的区别
ES6 新增了let
命令,用来声明变量。它的用法类似于var
,但是所声明的变量,只在let
命令所在的代码块内有效。
var
命令会发生“变量提升”现象,即变量可以在声明之前使用,值为undefined
。这种现象多多少少是有些奇怪的,按照一般的逻辑,变量应该在声明语句之后才可以使用。
为了纠正这种现象,let
命令改变了语法行为,它所声明的变量一定要在声明后使用,否则报错。
暂时性死区
只要块级作用域内存在let
命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响。
var tmp = 123;
if (true) {
tmp = 'abc'; // ReferenceError
let tmp;
}
上面代码中,存在全局变量tmp
,但是块级作用域内let
又声明了一个局部变量tmp
,导致后者绑定这个块级作用域,所以在let
声明变量前,对tmp
赋值会报错。
ES6 明确规定,如果区块中存在let
和const
命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。
总之,在代码块内,使用let
命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)。
不允许重复声明
let
不允许在相同作用域内,重复声明同一个变量。
const
声明一个只读的常量。一旦声明,常量的值就不能改变,const
一旦声明变量,就必须立即初始化。
预编译
-
创建AO对象
-
找形参和变量声明 作为AO对象的属性名 值是undefined
-
实参和形参相统一
-
找函数声明 相同会覆盖变量声明
function fn(a,c){ console.log(a) // function a(){} var a = 123; console.log(a) // 123 console.log(c) // function c(){} function a(){} if(false){ var d = 678 } console.log(d) // undefined console.log(b) // undefined var b = function (){} console.log(b) // function (){} function c(){} console.log(c) // function c(){} } fn(1,2) AO:{ a: undefined 1 function a(){} c: undefined 2 function c(){} b: undefined d: undefined }
let 不会变量提升,如果变量用let则会报错,a重复定义,c为函数c,d is not defined,Cannot access ‘b’ before initialization
闭包
闭包:有权访问另一个函数作用域中的变量的函数;一般情况就是在一个函数中包含另一个函数。
那闭包是怎样的一个表现形式呢?
第一,闭包是一个函数,而且存在于另一个函数当中
第二,闭包可以访问到父级函数的变量,且该变量不会销毁
闭包的实现原理,其实是利用了作用域链的特性,我们都知道作用域链就是在当前执行环境下访问某个变量时,如果不存在就一直向外层寻找,最终寻找到最外层也就是全局作用域,这样就形成了一个链条。
闭包作用
作用1:隐藏变量,避免全局污染
作用2:可以读取函数内部的变量
同时闭包使用不当,优点就变成了缺点:
缺点1:导致变量不会被垃圾回收机制回收,造成内存消耗
缺点2:不恰当的使用闭包可能会造成内存泄漏的问题
这里简单说一下,为什么使用闭包时变量不会被垃圾回收机制收销毁呢,这里需要了解一下JS垃圾回收机制;
JS规定在一个函数作用域内,程序执行完以后变量就会被销毁,这样可节省内存;使用闭包时,按照作用域链的特点,闭包(函数)外面的变量不会被销毁,因为函数会一直被调用,所以一直存在,如果闭包使用过多会造成内存销毁。
https://segmentfault.com/a/1190000021725949
this
当static或prototype method被调用的时候,如果没有对this赋值,那么this将是undefine状态。这和是否采用static模式无关,因为class类体中的代码已经默认执行static模式。
class Animal {
speak() {
return this;
}
static eat() {
return this;
}
}
let obj = new Animal();
obj.speak(); // Animal {}
let speak = obj.speak;
speak(); // undefined
Animal.eat() // class Animal
let eat = Animal.eat;
eat(); // undefined
class C {
a() {
console.log(this);
}
b = () => {
console.log(this);
};
}
c = new C();
c.a(); // C
f = c.a;
f(); // undefined
c.b(); // C
function C() {
this.a = ()=>{console.log(this)}
}
c = new C();
c.a(); // C
f = c.a;
f(); // C
如果以上代码是基于传统的function的语法,则this值是global object。
function Animal() {
}
Animal.prototype.eat = function(){
console.log(this)
}
let obj = new Animal();
obj.eat(); // Animal {}
let eat = obj.eat;
eat(); // window
var name = 222;
var a = {
name: 111,
say: function (){
console.log(this.name)
}
}
var fn = a.say
fn() // 222
a.say() // 111
var b = {
name: 333,
say: function (fn){
fn() // fn.call(window)
}
}
b.say(a.say) // 222
b.say = a.say
b.say() // 333
箭头函数中的this
this指向固定化。箭头函数没有自己的this,导致内部的this就是外层代码块的this。不能用作构造函数。
var x = 11;
var obj = {
x: 22,
say: ()=>{
console.log(this.x)
}
}
obj.say() // 11
var obj = {
birth: 22,
say: function(){
var b = this.birth;
var fn = ()=>{
console.log(this)
}
return fn()
}
}
obj.say() // obj
手写 map
map() 方法返回一个新数组,数组中的元素为原始数组元素调用函数处理后的值。
map() 方法按照原始数组元素顺序依次处理元素。
注意: map() 不会对空数组进行检测。
注意: map() 不会改变原始数组。
//array.map(function(currentValue,index,arr), thisValue)
Array.prototype.copyMap = function(fn, tothis){
let arr = this;
let result = [];
tothis = tothis || null;
for(let i = 0; i < arr.length; i++){
result.push(fn.call(tothis, arr[i], i , arr))
}
return result;
}
手写 filter
filter() 方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素。
注意: filter() 不会对空数组进行检测。
注意: filter() 不会改变原始数组。
//array.filter(function(currentValue,index,arr), thisValue)
Array.prototype.copyFilter = function(fn, tothis){
let arr = this;
let result = [];
tothis = tothis || null;
for(let i = 0; i < arr.length; i++){
if(fn.call(tothis, arr[i], i , arr)){
result.push(arr[i]);
}
}
return result;
}
手写 reduce
累加器
//array.reduce(function(total, currentValue, currentIndex, arr), initialValue)
Array.prototype.copyReduce = function(fn, val){
let arr = this;
let result = val? val: this[0];
let index = val? 0: 1;
for(let i = index; i < arr.length; i++){
result = fn(result, arr[i],i,arr);
}
return result;
}
var a = [2, 1, 3, 4];
console.log(a.copyReduce((t,ac) => {
return t + ac;
},100))
console.log(a.reduce((t,ac) => {
return t + ac;
},100))
手写 some
some() 方法用于检测数组中的元素是否满足指定条件(函数提供)。
some() 方法会依次执行数组的每个元素:
- 如果有一个元素满足条件,则表达式返回true , 剩余的元素不会再执行检测。
- 如果没有满足条件的元素,则返回false。
注意: some() 不会对空数组进行检测。
注意: some() 不会改变原始数组。
//array.some(function(currentValue,index,arr),thisValue)
Array.prototype.copySome = function(fn, tothis){
let arr = this;
tothis = tothis || null;
for(let i = 0; i < arr.length; i++){
if(fn.call(arr[i],i,arr)){
return true;
}
}
return false;
}
手写 concat
//arrayObject.concat(arrayX,arrayX,......,arrayX)
Array.prototype.copyConcat = function(){
console.log(arguments);
let result = [];
for(let i = 0; i < arguments.length; i++){
if(Array.isArray(arguments[i])){
for(let j = 0; j < arguments[i].length; j++){
result.push(arguments[i][j])
}
}else{
result.push(arguments[i])
}
}
return result
}
repeat函数
间隔wait秒执行n次
function repeat(fn,times, wait){
return function(){
for(let i =0; i < times; i++){
setTimeout(() =>{
fn(...arguments)
},wait*i)
}
}
}
const a = repeat(console.log,4, 1000)
a("hha");
once函数
只执行一次
function once(fn){
let flag = true;
return function(){
if(flag){
fn(...arguments);
flag = false;
}
}
}
const a = once(console.log)
a("hha");
a("hha");
a("hha");
深浅拷贝
数据分为基本数据类型(String, Number, Boolean, Null, Undefined,Symbol、BigInt)和 引用数据类型(统称为 Object类型
,细分的话有:Object
、Array
、Date
、RegExp
、Function…
)
-
基本数据类型的特点:直接存储在栈(stack)中的数据
-
引用数据类型的特点:存储的是该对象在栈中引用,真实的数据存放在堆内存里
引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。
浅拷贝只复制指向某个对象的指针而不复制对象本身,新旧对象还是共享同一块内存。
但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。
浅拷贝的方法:
1. 解构
new = [...arr]
2. Object.assign()
Object.assign() 方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。但是 Object.assign()进行的是浅拷贝,拷贝的是对象的属性的引用,而不是对象本身。
var obj = { a: {a: "kobe", b: 39} };
var initalObj = Object.assign({}, obj);
initalObj.a.a = "wade";
console.log(obj.a.a); //wade
3.Array.prototype.concat()
4.Array.prototype.slice()
深拷贝的方法
-
JSON
JSON.parse(JSON.stringify(arr));
但不能处理函数
-
递归
function用同一个地址并没有什么不妥
var clone = function (obj) { if(obj === null) return null if(typeof obj !== 'object') return obj; if(obj.constructor===Date) return new Date(obj); if(obj.constructor === RegExp) return new RegExp(obj); var newObj = new obj.constructor (); //保持继承链,克隆和之前的保持相同的类 for (var key in obj) { if (obj.hasOwnProperty(key)) { //不遍历其原型链上的属性 newObj[key] = clone(obj[key]); } } return newObj; };
数组扁平化
函数柯西化
防抖和节流
防抖和节流的区别:
防抖:一定操作频率下,只要有空余时间(操作与操作之间)就执行。
就是说频繁执行很多次后,以最后一次触发为起始时间计时,计时到了再执行。
每触发都会清空定时器,第一次触发会延迟执行。
节流:一段时间下只执行一次。
以触发的第一次为起始时间,并第一次触发就立即执行。
function debounce(fn, wait){
let timer;
return function(){
let that = this;
clearTimeout(timer)
timer = setTimeout(()=>{
fn.apply(that,arguments);
},wait);
}
}
btn.onclick = debounce(()=>{console.log("点击了")}, 200)
function tottle(fn, delay){
let pre = 0;
return function(){
let now = new Date();
if(now - pre > delay){
fn.apply(this,arguments);
pre = now;
}
}
}
btn.onclick = tottle(()=>{console.log("点击了")}, 1000)
this指向(call,apply,bind)
let c = {
name: "hhh"
}
function a(a){
console.log(this.name+a)
}
// 相对于在c这个对象中添加一个函数a的p变量,然后调用p,这时的this就是c
Function.prototype.mycall = function (obj=window, ...args){
obj.p = this; //这里的this是a
let result = obj.p(...args);
delete obj.p;
return result;
}
Function.prototype.myapply = function (obj=window,arr){
obj.p = this;
let result = obj.p(...arr);
delete obj.p;
return result;
}
a.mycall(c,"wwwwwww");
bind
Function.prototype.mybind = function (obj){
let that = this;
let a = Array.prototype.slice.call(arguments,1);
return function(){
let b = Array.prototype.slice.call(arguments); //具有柯西化
let result = that.apply(obj, a.concat(b));
return result;
}
}
//这样写没有考虑到bind可以配合new使用,this失效这一方面
考虑new的话
Function.prototype.mybind = function (obj){
let that = this;
a = Array.prototype.slice.call(arguments,1);
newfunc = function (){
console.log(this) // 因为new,新对象要绑定到函数调用的this
console.log(this instanceof newfunc) //一个对象是否为一个类的实例
let b = Array.prototype.slice.call(arguments);
c = a.concat(b);
if(this instanceof newfunc){
that.apply(this, c)
}else{
that.apply(obj, c)
}
}
// 需要让a的原型对象赋值给nn这个原型对象,使nn的实例能够调用a的属性
newfunc.prototype = that.prototype;
return newfunc
}
let nn = a.mybind(c,"aaa","bbbb");
let b = new nn("cccccc");
console.log(b.bb);
Promise
https://es6.ruanyifeng.com/#docs/promise
所谓Promise
,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果
Promise
对象有以下两个特点。
(1)对象的状态不受外界影响。Promise
对象代表一个异步操作,有三种状态:pending
(进行中)、fulfilled
(已成功)和rejected
(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise
这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。
(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise
对象的状态改变,只有两种可能:从pending
变为fulfilled
和从pending
变为rejected
。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise
对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
异步并发限制
API
new
-
new干了些啥
(1) 创建一个新对象;
(2) 将构造函数的作用域赋给新对象(因此 this 就指向了这个新对象) ;
(3) 执行构造函数中的代码(为这个新对象添加属性) ;
(4) 返回新对象。 -
模拟new
function P(name){ this.name = name; this.say = function(){ console.log(this.name); } } function newmy(){ let obj = {}; obj._proto_ = arguments[0].prototype; let a = Array.prototype.slice.call(arguments,1) arguments[0].apply(obj, a) return obj; } let o = newmy(P,"wowo") o.say()
Ajax
拖拽事件
或者使用 mousedown mousemove mouseup来计算offsetleft值
小数
继承
原型链继承
利用原型让一个引用类型继承另一个引用类型的属性和方法。
function Parent(){
this.name = "hh"
}
Parent.prototype.getName = function (){
return this.name;
}
function Child(){
}
Child.prototype = new Parent();
Child.prototype.constructor = Child
缺点:
- 实例共享引用类型
虽然原型链继承很强大, 但是存在一个问题, 最主要的问题是包含引用类型值的原型, 因为包含引用类型值的原型属性会被所有的实例共享, 而在通过原型来实现继承的时候, 原型实际变成了另外一个函数的实例(这里边就有可能存在引用类型)
function Parent(){
this.name = ["hh"]
}
Parent.prototype.getName = function (){
return this.name;
}
function Child(){
}
Child.prototype = new Parent();
Child.prototype.constructor = Child
child1 = new Child()
child2 = new Child()
child1.name[0] = "xixi"
console.log(child1.name) // xixi
console.log(child2.name) // xixi
- 在创建 Child 的子类的时候,无法像继承元素传递参数
构造函数继承
function Parent(name){
this.name = [name]
}
Parent.prototype.getName = function (){
return this.name;
}
// 在子类的构造函数中执行父类的构造函数并且为其绑定子类的this
function Child(){
Parent.call(this,"hhh")
}
child1 = new Child()
child2 = new Child()
child1.name[0] = "xixi"
console.log(child1.name) // xixi
console.log(child2.name) // hhh
console.log(child2.getName()) // 报错 并不能继承父类原型上的方法和属性
组合式继承
function Parent(name){
this.name = [name]
}
Parent.prototype.getName = function (){
return this.name;
}
// 在子类的构造函数中执行父类的构造函数并且为其绑定子类的this
function Child(){
Parent.call(this,"hhh")
}
Child.prototype = new Parent();
Child.prototype.constructor = Child
child1 = new Child()
child2 = new Child()
child1.name[0] = "xixi"
console.log(child1.name) // xixi
console.log(child2.name) // hhh
console.log(child2.getName()) // hhh
缺点:
构造函数执行两次
寄生式组合继承(class类的继承)
function Parent(name){
this.name = [name]
}
Parent.prototype.getName = function (){
return this.name;
}
// 在子类的构造函数中执行父类的构造函数并且为其绑定子类的this
function Child(){
Parent.call(this,"hhh")
}
//Child.prototype = Parent.prototype; // 这样Child原型添加方法或属性,parent也会添加
Child.prototype = Object.create(Parent.prototype); // 浅拷贝
Child.prototype.constructor = Child;
child1 = new Child()
child2 = new Child()
child1.name[0] = "xixi"
console.log(child1.name) // xixi
console.log(child2.name) // hhh
console.log(child2.getName()) // hhh
冒泡与捕获
事件传递有两种方式:冒泡与捕获。
事件冒泡
IE的事件流叫做事件冒泡,即事件从最具体的元素到不具体的元素。 好比气泡从水底下一直向上冒泡,像dom树一样,一直到根元素。
事件捕获
即从不具体的元素到具体的元素,与冒泡相反
Vue
深度监听
watch:{
"a.b.c":function(n,o){
}
}
watch:{
a:{
deep:true, // 意味着开启了深度监听,a里面任何数据变化都会触发handler函数
handler(){
}
}
}
keep-alive
如果组件需要缓存,或者说 A组件跳到B组件,再从B组件跳到A组件,但不需要A组件的数据重新获取,所以需要缓存。
router文件中配置meta属性
{
path:"/",
name:"Home",
component: Home
meta:{
isAlive:false
}
}
在路由中配置的meta可以直接使用
<router-view v-if="!$route.meta.isAlive"></router-view>
<keep-alive>
<router-view v-if="$route.meta.isAlive"></router-view>
</keep-alive>
缓存之后需要重新请求数据,则可以在生命周期activated里重新发送请求。
双向绑定
Object.defineProperty(data,"name",{
get: function(){
//获取监听对象的某个值
},
set: function(newval){
//设置监听对象的某个值
}
})
router传参
url传值,Parma和query参数传值
params传值 动态路由传值
{ path "/user/:id"} // 定义一个路由参数
<router-link to="/user/123"></router-link>
/user/:id
/user/123 123 就是 id
this.$route.params.id //取值 专门获取 :id 这种参数 params 参数
query传值,指通过?后面的拼接参数传值
{ path "/user"} // 定义一个路由参数
<router-link to="/user?id=123"></router-link>
this.$route.query.id //取值
前端鉴权一般思路
vue数据流
vue 、react 数据流是单向的 父–>子
监听Vuex的数据变化
// vuex中的state数据
state:{
count:0
}
// A组件中映射 state数据到计算属性
computed: {
...mapState(["count"])
}
// A组件监听 count计算属性的变化
watch:{
count(){
}
}
计算属性
用于简单运算,在模板{}中放入太多逻辑会让模板过重难以维护。
计算属性和方法:
两种方式最后结果是相同的。不同的是,计算属性是基于它们的响应式依赖进行缓存的。只在相关响应式依赖发生改变时它们才会重新求值。这就意味着只要message还没有发生改变,多次访问 计算属性会立即返回之前的结果,而不必再次执行函数。
computed里也有get 和 set方法。
watch能做到的computed也能做到,区别如下:
弹框提示等交互事件适用于watch,实时触发、数据计算和字符处理适用于computed
computed主要用于同步对数据的处理,而watch主要用于事件的派发,可异步。
computed具有缓存属性,只有当依赖的数据发生变化时,关联的数据才会变化,适用于计算或者格式数据的场景
watch监听数据,有关联但没有依赖,只要某个数据发生变化,就可以处理一些数据或者派发事件并同步/异步执行。
父子组件传值
<子组件 @ezi="父组件的方法" :val="父组件的数据"/>
- 子组件用props接受数据,props可以是数据也可以是对象。
- 用
this.$emit("ezi",参数)
兄弟组件传值
发布-订阅模式
Vuex
为什么在各个组件可以使用 this.$store
vuex是插件,可以看vue开发插件https://cn.vuejs.org/v2/guide/plugins.html
/*
install方法会在外界调用Vue.use的时候执行
并且在执行的时候会把Vue实例和一些额外的参数传递给我们
* */
const install = (Vue, options)=>{
// 给每一个Vue实例都添加一个$store属性
/*
在Vue中有一个名称叫做mixin方法, 这个方法会在创建每一个Vue实例的时候执行
所以我们可以通过mixin方法给每一个Vue实例添加$store属性
* */
Vue.mixin({
beforeCreate(){
/*
Vue在创建实例的时候会先创建父组件, 然后再创建子组件
* */
// console.log(this.$options.name);
/*
Root -> App -> HelloWorld
如果是根组件, 那么默认就有store
我们只需要将store变成$store即可
*/
if(this.$options && this.$options.store){
this.$store = this.$options.store;
}
// 如果不是根组件, 那么默认没有store
// 我们只需要将它父组件的$store赋值给它即可
else{
this.$store = this.$parent.$store;
}
}
});
}
class Store {
constructor(options){
this.options = options;
}
}
export default {
install,
Store
}
v-for 要配合 key 来使用
使用v-for更新已渲染的元素列表时,默认用就地复用策略;列表数据修改的时候,他会根据key值去判断某个值是否修改,如果修改,则重新渲染这一项,否则复用之前的元素;
什么时候使用$.nextTick()
vue中数据和dom渲染由于是异步的,所以,要让dom结构随数据改变这样的操作都应该放进this.$nextTick()
的回调函数中
Computed和Watch
computed: 是计算属性,依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值发生改变,下一次获取 computed 的值时才会重新计算 computed 的值;
watch: 更多的是「观察」的作用,类似于某些数据的监听回调 ,每当监听的数据变化时都会执行回调进行后续操作;
运用场景:
当我们需要进行数值计算,并且依赖于其它数据时,应该使用 computed,因为可以利用 computed 的缓存特性,避免每次获取值时,都要重新计算;
当我们需要在数据变化时执行异步或开销较大的操作时,应该使用 watch,使用 watch 选项允许我们执行异步操作 ( 访问一个 API ),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的。
MVVM和MVC
主要就是 mvc 中 Controller 演变成 mvvm 中的 viewModel。mvvm 主要解决了 mvc 中大量的 DOM 操作使页面渲染性能降低,加载速度变慢,影响用户体验。和当 Model 频繁发生变化,开发者需要主动更新到 View 。
mvc 简单来说就是单向数据改变吧,默认只是 model 的改变,控制 view。而 mvvm 是双向 model 和 view 自动更新
路由的原理
大型单页应用最显著特点之一就是采用的前端路由系统,通过改变URL
,在不重新请求页面的情况下,更新页面视图。
更新视图但不重新请求页面,是前端路由原理的核心之一,目前在浏览器环境中这一功能的实现主要有2
种方式:
- 利用
URL
中的hash
("#"
); - 利用
History interface
在HTML5
中新增的方法;
vue-router
是Vue.js
框架的路由插件,它是通过mode
这一参数控制路由的实现模式的:
const router=new VueRouter({
mode:'history',
routes:[...]
})
mode 参数:
- 默认hash
- history 注:如果浏览器不支持history新特性,则采用hash方式
- 如果不在浏览器环境则使用abstract(node环境下)
https://www.cnblogs.com/gaosirs/p/10606266.html
Vue项目优化
https://blog.csdn.net/qq_37939251/article/details/100031285
v-if和v-for不建议同时使用
https://zhuanlan.zhihu.com/p/147882950?from_voters_page=true
在官方文档中明确指出v-for和v-if不建议一起使用。
原因:v-for比v-if优先级高,所以使用的话,每次v-for都会执行v-if,造成不必要的计算,影响性能,尤其是当之需要渲染很小一部分的时候。
那难道就没有更好的解决办法,回答:当然是有解决方法的;我们可以使用computed
常用的事件修饰符
https://www.cnblogs.com/xiaoyaoxingchen/p/10405207.html
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kFN6QTCR-1623081350862)(C:\Users\81006\AppData\Roaming\Typora\typora-user-images\image-20210525194359436.png)]
参考
http://www.blogjava.net/zhanglongsr/articles/291186.html
https://shuangxunian.gitee.io/
https://blog.csdn.net/weixin_39818813/article/details/106515330
https://www.jianshu.com/p/88bb82718517