一、 函数
1. 函数中的 this 指向
- 普通函数中的 this 指向 window
//普通函数
function f1() {
console.log(this); // window
}
f1();
2. 定时器方法中的 this 指向 window
//定时器方法
setInterval(function () {
console.log(this); // window
},2000);
3. 构造方法的 this 指向实例对象
//构造函数
function Person() {
console.log(this); // Person {}
}
var per = new Person();
4. 对象方法中的 this 指向实例对象
//原型对象方法
//对象方法
function Person() {
console.log(this);
this.sayHi = function () {
console.log(this); // Person {}
}
}
var per = new Person();
per.sayHi();
5.原型对象方法中的 this 指向实例对象
//原型对象方法
function Person() {}
Person.prototype.sayHi = function () {
console.log(this);
};
var per = new Person();
per.sayHi();
6. 箭头函数的 this 指向外层作用域 的 this 的值
var length = 10
let obj = {
length: 100,
test: () =>{
console.log(this.length)
}
}
obj.test()
7. 调用者 是谁 this 指向谁,没有调用者 this 指向 window
var length = 10;
function test () {
console.log(this.length)
}
let obj = {
length: 100,
test: test
}
obj.test() // 调用者为 obj,this 指向 obj, 输出结果为 100
var length = 10;
function test () {
console.log(this.length)
}
let obj = {
length: 100,
test: test
}
let f1 = obj.test;
f1() // 没有调用都, this 指向 window, 输出结果为 10
var length = 10;
function test () {
console.log(this.length)
}
let arr = [test, 2, 3, 4, 6];
arr[0]() // 调用者为 arr, this 指向 arr, this.length 输出得到arr 的长度,所以为 5
var length = 10;
function test() {
console.log(this.length)
}
let obj = {
length: 100,
test: function(test) {
test(); // 这里 test 没有调用者,this 指向 window, 输出 10
arguments[0]() // arguments[0]() 中 this 指向的是 参数这个伪数组,this.length 得到的就是参数的长度,为3
}
}
obj.test(test, 2,3)
2. 箭头函数有哪些特点?
var fn1 = (a, b) => {
return a + b
}
(a, b) => {
return a + b
}
箭头函数是匿名函数
箭头函数的 this 指向外层作用域的 this 的值
箭头函数不绑定 argument 是,而用剩余参数…rest 解决
箭头函数不能用作构造函数
箭头函数没有原型属性
let func = (...args) => {
console.log('函数的参数是:', args);
}
func(1, 2, 3); // 函数的参数是: [1, 2, 3]
二、数据类型
1. JS 数据类型
数据类型包括两部分:
- 基本数据类型: Undefined、Null、Boolean、Number 和 String
- 引用数据类型: Object (包括 Object 、Array 、Function)
- ECMAScript 2015 新增:Symbol(创建后独一无二且不可变的数据类型 )
基本数据类型:直接存储在栈内存中,占据空间小,大小固定,属于被频繁使用的数据。
引用数据类型:同时存储在栈内存与堆内存中,占据空间大,大小不固定。
引用数据:类型将指针存在栈中,将值存在堆中。 当我们把对象值赋值给另外一个变量时,
复制的是对象的指针,指向同一块内存地址
2. 基本和引用数据类型的不同
基本数据类型指的是简单的数据段。
引用数据类型指的是有多个值构成的对象。 。
3. 判断一个值是什么类型
- typeof 运算符
typeof 一般只能返回如下几个结果:
"number"、"string"、"boolean"、"object"、"function" 和 "undefined"。
运算数为数字: typeof(x) = "number"
字符串: typeof(x) = "string"
布尔值: typeof(x) = "boolean"
对象,数组和null typeof(x) = "object"
函数: typeof(x) = "function"
null: typeof null 的结果是Object。
- instanceof 运算符
instanceof 用于判断一个变量是否某个对象的实例。
如 :var a=new Array();
alert(a instanceof Array); // true,
同时 alert(a instanceof Object) //也会返回 true;
这是因为 Array 是 object 的子类。
- Object.prototype.toString 方法
var arr=[1,2];
//直接对一个数组调用toString()
arr.toString();// "1,2"
//通过call指定arr数组为Object.prototype对象中的toString方法的上下文
Object.prototype.toString.call(arr); //"[object Array]"
4. null 和 undefined 有什么区别
null 表示一个对象被定义了,值为“空值”;
初始赋值 为 null ,var a = null;表明将要赋值为对象;
结束前赋值为 null ,a= null;让对象成为垃圾对象,好被浏览器的垃圾回收器回收。
undefined 表示不存在这个值。
(1)变量被声明了,但没有赋值时,就等于 undefined。
(2) 调用函数时,应该提供的参数没有提供,该参数等于 undefined。
(3)对象没有赋值的属性,该属性的值为 undefined。
(4)函数没有返回值时,默认返回 undefined。
5. 判断一个变量 arr 的话是数组
arr instanceof Array // true
arr.constructor == Array // true
console.log(Array.isArray(arr)) // true
console.log(arr.__proto__ === Array.prototype) // 通过对象的原型方式来判断
6. “ ===”、“ ==”的区别?
==,当且仅当两个运算数相等时,它返回 true,即不检查数据类型
===,只有在无需类型转换运算数就相等的情况下,才返回 true,需要检查数据类型
7. 两个对象如何比较
Object 类型的比较是非常重要的基础知识,这里可以总结为四种方法:引用对比、手动对比、浅对比、深对比。
引用对比 下面三种对比方式用于 Object,皆在引用相同是才返回
true:
=====Object.is()
const hero1 = {
name: "Batman",
};
const hero2 = {
name: "Batman",
};
hero1 === hero1; // => true
hero1 === hero2; // => false
hero1 == hero1; // => true
hero1 == hero2; // => false
Object.is(hero1, hero1); // => true
Object.is(hero1, hero2); // => false
手动对比 写一个自定义函数,按照对象内容做自定义对比也是一种方案:
如果要对比的对象 key 不多,或者在特殊业务场景需要时,这种手动对比方法其实还是蛮实用的。
但这种方案不够自动化,所以才有了浅对比。
function isHeroEqual(object1, object2) {
return object1.name === object2.name;
}
const hero1 = {
name: "Batman",
};
const hero2 = {
name: "Batman",
};
const hero3 = {
name: "Joker",
};
isHeroEqual(hero1, hero2); // => true
isHeroEqual(hero1, hero3); // => false
浅对比 浅对比函数写法有很多,不过其效果都是标准的,下面给出了一种写法: 可以看到,浅对比就是将对象每个属性进行引用对比,算是一种性能上的平衡,尤其在 redux 下有特殊的意义。
如果对象层级再多一层,浅对比就无效了,此时需要使用深对比。
function shallowEqual(object1, object2) {
const keys1 = Object.keys(object1);
const keys2 = Object.keys(object2);
if (keys1.length !== keys2.length) {
return false;
}
for (let key of keys1) {
if (object1[key] !== object2[key]) {
return false;
}
}
return true;
}
const hero1 = {
name: "Batman",
realName: "Bruce Wayne",
};
const hero2 = {
name: "Batman",
realName: "Bruce Wayne",
};
const hero3 = {
name: "Joker",
};
shallowEqual(hero1, hero2); // => true
shallowEqual(hero1, hero3); // => false
深对比 深对比就是递归对比对象所有简单对象值,遇到复杂对象就逐个 key 进行对比,以此类推。
可以看到,只要遇到 Object 类型的 key,就会递归调用一次
deepEqual进行比较,否则对于简单类型直接使用!==引用对比。
值得注意的是,数组类型也满足
typeof object === "object"的条件,且Object.keys可以作用于数组,且object[key]也可作用于数组,因此数组和对象都可以采用相同方式处理。
有了深对比,再也不用担心复杂对象的比较了:
但深对比会造成性能损耗,不要小看递归的作用,在对象树复杂时,深对比甚至会导致严重的性能问题。
function deepEqual(object1, object2) {
const keys1 = Object.keys(object1);
const keys2 = Object.keys(object2);
if (keys1.length !== keys2.length) {
return false;
}
for (const key of keys1) {
const val1 = object1[key];
const val2 = object2[key];
const areObjects = isObject(val1) && isObject(val2);
if (
(areObjects && !deepEqual(val1, val2)) ||
(!areObjects && val1 !== val2)
) {
return false;
}
}
return true;
}
function isObject(object) {
return object != null && typeof object === "object";
}
const hero1 = {
name: "Batman",
address: {
city: "Gotham",
},
};
const hero2 = {
name: "Batman",
address: {
city: "Gotham",
},
};
deepEqual(hero1, hero2); // => true
8. 判断对象是否存在
if (!myObj) {
myObj = { };
}
但是,运行这段代码,浏览器会直接抛出ReferenceError错误,导致运行中断。请问错在哪里?
对了,if 语句判断 myObj 是否为空时,这个变量还不存在,所以才会报错。改成下面这样,就能正确运行了。
if (!myObj) {
var myObj = { };
}
为什么加了一个var以后,就不报错了?难道这种情况下,if语句做判断时,myObj就已经存在了吗?
要回答这个问题,就必须知道 Javascript 解释器的工作方式。Javascript 语言是"先解析,后运行",解析时就已经完成了变量声明,所以上面的代码实际等同于:
var myObj;
if (!myObj) {
var myObj = { };
}
因此,if 语句做判断时,myObj 确实已经存在了,所以就不报错了。这就是 var 命令的"代码提升"(hoisting)作用。Javascript 解释器,只"提升" var 命令定义的变量,对不使用 var 命令、直接赋值的变量不起作用,这就是为什么不加 var 会报错的原因。
除了var命令,还可以有另一种改写,也能得到正确的结果:
window 是 javascript 的顶层对象,所有的全局变量都是它的属性。所以,判断 myobj 是否为空,等同于判断 window 对象是否有 myobj 属性,这样就可以避免因为 myObj 没有定义而出现 ReferenceError 错误。不过,从代码的规范性考虑,最好还是对第二行加上var:
if (!window.myObj) {
myObj = { };
}
if (!window.myObj) {
var myObj = { };
}
或者写成这样:
if (!window.myObj) {
window.myObj = { };
}
上面这种写法的缺点在于,在某些运行环境中(比如V8、Rhino),window未必是顶层对象。所以,考虑改写成:
在全局变量的层面中,this关键字总是指向顶层变量,所以就可以独立于不同的运行环境。
if (!this.myObj) {
this.myObj = { };
}
但是,上面这样写可读性较差,而且this的指向是可变的,容易出错,所以进一步改写:
用自定义变量global表示顶层对象,就清楚多了。
var global = this;
if (!global.myObj) {
global.myObj = { };
}
还可以使用 typeof 运算符,判断myObj是否有定义。这是目前使用最广泛的判断javascript对象是否存在的方法。
if (typeof myObj == "undefined") {
var myObj = { };
}
由于在已定义、但未赋值的情况下,myObj的值直接等于undefined,所以上面的写法可以简化:\
这里有两个地方需要注意,首先第二行的var关键字不能少,否则会出现ReferenceError错误,其次undefined不能加单引号或双引号,因为这里比较的是undefined这种数据类型,而不是"undefined"这个字符串。
if (myObj == undefined) {
var myObj = { };
}
上面的写法在"精确比较"(===)的情况下,依然成立:
if (myObj === undefined) {
var myObj = { };
}
根据javascript的语言设计,undefined == null,所以比较myObj是否等于null,也能得到正确结果:
不过,虽然运行结果正确,但是从语义上看,这种判断方法是错的,应该避免。因为null指的是已经赋值为null的空对象,即这个对象实际上是有值的,而undefined指的是不存在或没有赋值的对象。因此,这里只能使用"比较运算符"(==),如果这里使用"精确比较运算符"(===),就会出错。
if (myObj == null) {
var myObj = { };
}
还可以使用in运算符,判断myObj是否为顶层对象的一个属性:
if (!('myObj' in window)) {
window.myObj = { };
}
最后,使用hasOwnProperty方法,判断myObj是否为顶层对象的一个属性:
if (!this.hasOwnProperty('myObj')) {
this.myObj = { };
}
9. 判断对象上是否含有某个属性
方法一: 对象.hasOwnProperty('属性'); 返回值为 布尔值
注意:
- 和原型没关系 ,不会查找原型上的属性。
- 只判断自身属性的场景。
let obj = { name: 'll', age: 189 };
console.log(obj.hasOwnProperty('age')); // true
console.log(obj.hasOwnProperty('name')); // true
console.log(obj.hasOwnProperty('sex')); // false
方法二: 点( . )或者方括号( [ ] )
使用 对象.属性 != undefined 来判断对象是否含有某个属性。或者 对象['属性'] != undefined 来判断对象是否含有某个属性。
通过 点 或者方括号可以获取对象的属性值,如果对象上不存在该属性,则会返回undefined。
不存在只的是对象和对象的原型链上都不存在,如果原型链上有该属性则返回原型链上的属性。
注意:
- 会查找原型链上的属性。
- 不能用在对象的属性值存在,属性值为 undefined 的场景下。
var obj = {
name: 'll',
age: 189,
};
console.log(obj['name']); // ll
console.log(obj.age); // 189
console.log(obj.sex); // undefined
// 可以查找原型上的属性
console.log(obj.toString); // ƒ toString() { [native code] }
obj.data = undefined;
console.log(obj.data); // undefined
方法三: in 运算符, 返回布尔值
注意:如果指定的属性在指定的对象或其原型链中,则 in 运算符 返回true。
数组使用时,可以使用 数组下标 来判断,但是不能使用数组元素来判断。 Symbol.iterator in 数组; 返回true (数组可迭代,只在ES2015+上有效).也可以判断内置对象上的属性。
使用 delete 运算符删除了一个属性,则 in 运算符对所删除属性返回 false。
只是将一个属性的值赋值为 undefined,而没有删除它,则 in 运算仍然会返回true。
对于字符串的属性也可以判断,只是必须是字符串对象 new String() 创建的字符串。
// 数组
var arr = ['blue', 'pink', 'yellow'];
console.log(0 in arr); // true
console.log(1 in arr); // true
console.log(3 in arr); // falae
// 必须使用索引号,而不是数组元素的值
console.log('pink' in arr); // false
// 数组上有 length 属性
console.log(length in arr); // true
// 也可以判断数组原型上的属性
console.log('concat' in arr); // true
// Symbol.iterator in trees // 返回true
console.log(Symbol.iterator in arr) // 返回true (数组可迭代,只在ES2015+上有效)
// 内置对象
console.log("PI" in Math); // 返回true
// 自定义对象
var mycar = { make: "Honda", model: "Accord", year: 1998 };
console.log("make" in mycar); // 返回true
console.log("model" in mycar); // 返回true
// in右操作数必须是一个对象值。例如,你可以指定使用String构造函数创建的字符串,但不能指定字符串文字。
var color1 = new String("green");
console.log("length" in color1); // 返回true
var color2 = "coral";
console.log("length" in color2) // 报错(color2不是对象)Cannot use 'in' operator to search for 'length' in coral
// 使用 delete 运算符删除了一个属性,则 in 运算符对所删除属性返回 false。
var mycar = { make: "Honda", model: "Accord", year: 1998 };
delete mycar.make;
"make" in mycar; // 返回false
var trees = new Array("redwood", "bay", "cedar", "oak", "maple");
delete trees[3];
3 in trees; // 返回false
// 只是将一个属性的值赋值为undefined,而没有删除它,则 in 运算仍然会返回true
var mycar = { make: "Honda", model: "Accord", year: 1998 };
mycar.make = undefined;
"make" in mycar; // 返回true
var trees = new Array("redwood", "bay", "cedar", "oak", "maple");
trees[3] = undefined;
3 in trees; // 返回true
// 如果一个属性是从原型链上继承来的,in 运算符也会返回 true。
"toString" in {}; // 返回true
三、Ajax
1. 什么是 ajax?
一. 什么是 ajax?
ajax(异步javascript xml) 能够刷新局部网页数据而不是重新加载整个网页。Form表单会
刷新整个网页
原生Ajax的创建过程
1.创建xhr 核心对象
var xhr=new XMLHttpRequest();
2.调用open 准备发送
参数一:请求方式
参数二: 请求地址
参数三:true异步,false 同步
xhr.open('post','http://www.baidu.com/api/search',true)
3.如果是post请求,必须设置请求头。
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
4.调用send 发送请求 (如果不需要参数,就写null)
xhr.send('user=tom&age=10&sex=女')
5.监听异步回调 onreadystatechange
判断readyState 为4 表示请求完成
判断status 状态码 为 200 表示接口请求成功
responeseText 为相应数据。字符串类型。
xhr.onreadystatechange=function(){
if(xhr.readyState==4){
if(xhr.status==200){
console.log(xhr.responseText);
var res=JSON.parse(xhr.responseText);
console.log(res);
if(res.code==1){
modal.modal('hide');
location.reload();
}
}
备注:如果是post请求,想要传json格式数据。
设置请求头
1.xhr.setRequestHeader('Content-Type', 'application/json')
open发送数据
2.xhr.open({_id:xxx,user:xxxx,age:xxxx})
2. 为什么要作 Ajax 二次封装
封装步骤:www.jianshu.com/p/dedb17d9e…
好处有:
1,二次封装后的 API 更加简洁,更符合个人的使用习惯;
2,可以对 ajax 操作做一些统一处理,比如追加随机数或其它参数。
同时在工作中,我们还会发现,有一些 ajax 请求的数据,对实时性要求不高,即使我们把第一次请求 到的这些数据缓存起来,然后当相同请求再次发起时直接拿之前缓存的数据返回也不会对相关功能有 影响,通过这种手工的缓存控制,减少了 ajax 请求,多多少少也能帮助我们提高网页的性能
3. Ajax get、post的区别
1.get 传参方式是通过地址栏 URL 传递,是可以直接看到 get 传递的参数,post 传参方式参数 URL 不可见,get 把请求的数据在 URL 后通过 ?连接,通过 & 进行参数分割。psot 将参数存放在 HTTP 的包体内
2.get 传递数据是通过 URL 进行传递,对传递的数据长度是受到 UR L大小的限制,URL 最大长度是 2048 个字符。post 没有长度限制
3.get 后退不会有影响,post 后退会重新进行提交
4.get 请求可以被缓存,post 不可以被缓存
5.get 请求只 URL 编码,post 支持多种编码方式
6.get 请求的记录会留在历史记录中,post 请求不会留在历史记录
7.get 只支持 ASCII 字符,post 没有字符类型限制
4. HTTP常见的状态码?
100 Continue 继续,一般在发送 post 请求时,已发送了 http header 之后服务端将返回此信息,
表示确认,之后发送具体参数信息
200 OK 正常返回信息
201 Created 请求成功并且服务器创建了新的资源
202 Accepted 服务器已接受请求,但尚未处理
301 Moved Permanently 请求的网页已永久移动到新位置。
302 Found 临时性重定向。
303 See Other 临时性重定向,且总是使用 GET 请求新的 URI。
304 Not Modified 自从上次请求后,请求的网页未修改过。
400 Bad Request 服务器无法理解请求的格式,客户端不应当尝试再次使用相同的内容发起请求。
401 Unauthorized 请求未授权。
403 Forbidden 禁止访问。
404 Not Found 找不到如何与 URI 相匹配的资源。
405 只支持get请求,不支持post请求
500 Internal Server Error 最常见的服务器端错误。
503 Service Unavailable 服务器端暂时无法处理请求(可能是过载或维护)。
5. ajax 的原理?
由客户端请求 ajax 引擎,再由 ajax 引擎请求服务器,服务器作出一系列响应之后返回给 ajax 引擎,由 ajax 引擎决定将这个结果写入到客户端的什么位置。实现页面无刷新更新数据。
6. ajax 优缺点
优点
1.无刷新更新数据
2、异步与服务器通信
3、前端和后端负载平衡
4、基于标准被广泛支持
5、界面与应用分离
缺点:
1、ajax不能使用Back和history功能,即对浏览器机制的破坏。
2、安全问题 ajax暴露了与服务器交互的细节
3、对收索引擎的支持比较弱
4、破坏程序的异常处理机制
5、违背URL和资源定位的初衷
6、ajax不能很好的支持移动设备
7、太多客户端代码造成开发上的成本
7. 请求方法有哪些
get:请求指定页面信息,并返回实体主体。
head:类似于get请求,只不过返回的响应中没有具体的内容,用于获取报头
post:向指定资源提交数据进行处理请求(例如提交表单或上传文件),数据包含在请求体中。
post: 请求可能会导致新的资源的建立或已有资源的修改。
put:从客户端向服务器传送的数据取代指定的文档的内容。
delete:请求服务器删除指定的页面。 connect:HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。
options:允许客户端查看服务器的性能。
trace:回显服务器收到的请求,主要用于测试或诊断。
四、闭包和内存泄露
1. 闭包的概念
闭包函数:声明在一个函数中的函数,叫做闭包函数。
闭包:内部函数总是可以访问其所在的外部函数中声明的参数和变量, 即使在其外部函数被返回(寿命终结)了之后。
图解:
function createFn() {
let sum = 0;
function changeNum(val) {
return sum += val;
}
return {
addNum: function() {
changeNum(1)
},
redNum: function() {
changeNum(-1)
},
getSum: function() {
return sum;
}
}
}
let result = createFn();
let c = result.addNum()
console.log(result.getSum(), '----')
2. 闭包的三个特点
1.函数嵌套函数
2.在闭包里面,内部的函数可以访问到外部函数作用域内的变量,但是外部的函数不能访问内部函数作用域内的变量
3.参数和变量不会被垃圾回收机制回收
以前不明白为什么它会每次递加,因为a函数被存进了变量c 他是[全局变量], 不会被垃圾回收机制回收,下次再调用 C() 的时候 c 的值在内存中保存,所以每次都是在原有的基础上加一
function a() {
var num = 0;
function b() {
console.log(++num);
};
return b;
};
var c = a();
c() // 1
c() //2
为什么闭包不会被回收:
因为他本身就是建立在一个函数的内部作用域的子函数,由于可访问上级作用域的原因,即使上级函数执行完,作用域也不会被清除,这时子函数,就是闭包,就拥有了访问上级作用域中变量的权限,即使上级函数执行完,作用域内的值也不会被销毁
3. 闭包应用场景
应用场景: 设置私有变量的方法,如(防抖、节流、库的封装,保证数据私有性)
不适用场景:返回闭包的函数是个非常大的函数
4. 闭包的优点和缺点
优点: 可以避免变量的污染, 变量私有化,保存变量,常驻内存
缺点: 就是常驻内存,会增大内存使用量,使用不当会造成内存泄漏
5. 什么是内存泄漏
声明函数和变量,是会占用内存的。一般情况,内存会自动清除垃圾(不用的变量)。当有些变量无法被清楚的时候,称之为内存泄漏。
6. 哪些操作会造成内存泄漏?
内存泄漏指任何对象在不再拥有或不需要它之后依然存在,即这部分内存用完之后并没有返回到内存池。
1. setTimeout第一个参数是字符串而不是函数的时候就会造成内存泄漏
2. 闭包
3. 控制台日志
4. 循环(两个对象彼此引用且彼此保留)
5. 全局变量
如果你不断的创建全局变量,不管有没有用到他们,他们都将滞留在程序的整个执行过程中。
6. 事件监听器
在页面中创建事件监听器,但是在页面跳转时,又忘记移除这些监听器,那么也可能导致内存泄漏。
7. javascript 的内存(垃圾)回收机制?
js具有自动回收机制,垃圾收集器会按照固定的时间间隔周期性的执行。
回收两种方式:
(一)标记清除
原理:当变量进入环境时,将这个变量定义为“进入环境”,将变量离开环境时,将 其标记为“离开环境”,标记“离开环境”的就回收内存。
工作流程:
1. 垃圾回收器。在运行的时候给存储在内存中的所有变量都加上标记。
2. 去掉环境中的变量以及被环境中的变量引用的变量(闭包)的标记。
3. 此时仍有标记的变量视为即将要删除的变量
4. 垃圾回收器完成内存清除的工作,销毁那些带标记的值并回收他们所占的内存空间。
(二) 引用计数
工作原理:跟踪记录每个值被引用的次数
工作流程:
1. 声明一个变量并将一个引用类型值赋值给这个变量,这个引用类型值的引用次数就是1
2. 同一个引用类型值又被赋值给另一个变量,这个引用类型值的引用次数就加1
3. 当包含这个引用类型值的变量又取得了另一个值,这个引用类型值的引用次数就会减1
4. 当引用次数变为0时,说明没法访问这个值了。
5. 当垃圾回收器下一次运行时就会释放引用次数为0的值所占的内存空间。
8. 垃圾回收规则
(1)全局变量不会被回收
(2)只要被另一个作用域引用就不会被销毁
(3)局部变量会被回收,也就是函数一旦运行完,函数内部的变量就会被销毁
五、定时器、var 、eval、call、export
1. 定时器
在js中的定时器分两种:1、setTimeout() 2、setInterval()
1.setTimeOut() 只在指定时间后执行一次
function hello(){
alert("hello");
}
//使用方法名字执行方法
var t1 = window.setTimeout(hello,1000);
var t2 = window.setTimeout("hello()",3000);//使用字符串执行方法
window.clearTimeout(t1);//去掉定时器</pre>
2.setInterval() 在指定时间为周期循环执行
/实时刷新 时间单位为毫秒
setInterval('refreshQuery()',8000);
/* 刷新查询 */
function refreshQuery(){
console.log('每8秒调一次')
}
对于这两个方法,需要注意的是如果要求在每隔一个固定的时间间隔后就精确地执行某动作,
那么最好使用 setInterval,
而如果不想由于连续调用产生互相干扰的问题,尤其是每次函数的调用需要繁重的计算以及很长的处理时间,那么最好使用 setTimeout
3. settimeout 0
function a() {
setTimeout( function(){
alert(1)
}, 0);
alert(2);
}
a();
代码中的 setTimeout 设为 0,也就是延迟 0ms,看上去是不做任何延迟立刻执行,即依次弹出 “1”、“2”。
但实际的执行结果确是 “2”、“1”。其中的原因得从 setTimeout 的原理说起:
JavaScript 是单线程执行的,也就是无法同时执行多段代码,当某一段代码正在执行的时候,所有后续
的任务都必须等待,形成一个队列,一旦当前任务执行完毕,再从队列中取出下一个任务。
这也常被称为 “阻塞式执行”。所以一次鼠标点击,或是计时器到达时间点,或是 Ajax 请求完成触发了
回调函数,这些事件处理程序或回调函数都不会立即运行,而是立即排队,一旦线程有空闲就执
行。假如当前 JavaScript 进程正在执行一段很耗时的代码,此时发生了一次鼠标点击,那么
事件处理程序就被阻塞,用户也无法立即看到反馈,事件处理程序会被放入任务队列,直到
前面的代码结束以后才会开始执行。如果代码中设定了一个 setTimeout,那么浏览器便会在
合适的时间,将代码插入任务队列,如果这个时间设为 0,就代表立即插入队列,但不是立即执行,
仍然要等待前面代码执行完毕。所以setTimeout 并不能保证执行的时间,是否及时执行取决于
JavaScript 线程是拥挤还是空闲。
setTimeout延时0毫秒的作用
浏览器会在执行完当前任务队列中的任务,再执行setTimeout队列中积累的任务。
通过设置任务在延迟0毫秒后执行,就能改变任务执行的先后顺序,延迟该任务发生,使之异步执行。
浏览器执行js任务,,分为:
1、同步任务
2、异步任务(有微任务和宏任务)
a. 微任务
b.宏任务
同步任务先执行,然后执行异步任务的微任务,再执行 宏任务
2. eval是做什么的?
它的功能是把对应的字符串解析成 JS 代码并运行; eval("2+3");//执行加运算,并返回运算值。
应该避免使用 eval,不安全,非常耗性能(2次,一次解析成 js 语句,一次执行)。
3. var、let、const 区别
1. var 声明的变量会挂载在 window 上,而 let 和 const 声明的变量不会
2. var 声明变量存在变量提升,let 和 const 不存在变量提升,
变量提升即将变量声明提升到它所在作用域的最开始的部分。既全局变量.
3. let 和 const 声明形成块作用域
if(1){
var a = 100;
let b = 10;
const c = 1;
}
console.log(a); // 100
console.log(b) // 报错:b is not defined ===> 找不到b这个变量
console.log(c) // 报错:c is not defined ===> 找不到c这个变量
4. 同一作用域下 let 和 const 不能声明同名变量,而 var 可以
var a = 100;
console.log(a); // 100
var a = 10;
console.log(a); // 10
let a = 100;
let a = 10;
// 控制台报错:Identifier 'a' has already been declared ===> 标识符 a 已经被声明了。
5. const 一旦声明必须赋值,不能使用 null 占位。声明后不能修改,除非声明的是复合类型数据。
想让复合类型数据也不能被修改,可以使用 冻结对象 Object.freeze()
const a = 100;
const list = [];
list[0] = 10;
console.log(list); // [10]
const obj = {a:100};
obj.name = 'apple';
obj.a = 10000;
console.log(obj); // {a:10000,name:'apple'}
4. call() 与 apply() 的区别与作用
call,apply 都属于 Function.prototype 的一个方法,它是 JavaScript 引擎内在实现的,因为属于
Function.prototype,所以每个 Function 对象实例(就是每个方法)都有call,apply 属性。既然作为
方法的属性,那它们的使用就当然是针对方法的了,这两个方法是容易混淆的,因为它们的作用一样,
只是使用方式不同。
1. call()方法和apply()方法的作用相同:改变this指向。
2、他们的区别在于接收参数的方式不同:
call():第一个参数是 this 值没有变化,变化的是其余参数都直接传递给函数。在使用call()方法时,传递给函数的参数必须逐个列举出来。
apply():传递给函数的是参数数组
bind(): 传递的是一个对象
window.color = "red";
var o = { color: "blue" };
function sayColor(){
alert(this.color);
}
var objectSayColor = sayColor.bind(o);
objectSayColor(); //blue
5. export 和 export default 的区别
一、export的使用
1. export 可以直接输出
export let words = 'hello world!!!'
export function output() {
// ...
}
2. export 也可以先定义再输出
let firstWords = 'hello'
let secondWords = 'world'
let thirdWords = '!!!'
function output() {
// ...
}
export {firstWords, secondWords, thirdWords, output}
二、export default 的使用
1. export default 用于规定模块的默认对外接口
2.很显然默认对外接口只能有一个,所以 export default 在同一个模块中只能出现一次
3. export default 只能直接输出,不能先定义再输出。
4.其在 import 方式上也和 export 存在一定区别
(1) export 的输出与 import 输入
export function output() {
// ...
}
import {output} from './example'
(2) export default 的输出与 import 输入
export default function output() {
// ...
}
import output from './example'
从以上两种 import 方式即可看出,export default 的 import 方式不需要使用大括号包裹。
因为对于 export default 其输出的本来就只有一个接口,提供的是模块的默认接口,自然不需要使用大括号包裹。
6. import 和 require 区别
第一:两者的加载方式不同
require 是在运行时加载,require 可以理解为一个全局方法,所以它甚至可以进行下面这样的骚操作,是一个方法就意味着可以在任何地方执行,\
而 import 是在编译时加载, 而 import 必须写在文件的顶部。
var a = require(a() + '/ab.js')
1.require 的性能相对于 import 稍低,因为 require 是在运行时才引入模块并且还赋值给某个变量,而 import 只需要依据 import 中的接口在编译时引入指定模块所以性能稍高。
3、在 commom.js 中 module.exports 之后 导出的值就不能再变化,但是在 es6 的 export 中是可以的。
var a = 6
export default {a}
a = 7 // 在es6中的export可以
var a = 6
module.exports = a
a = 7 // 在common.js中,这样是错误的
4、require/exports 方式的写法比较统一
// require
const module = require('module')
// exports
export.fs = fs
module.exports = fs
5.import/export 方式的写法就相对丰富些
// import
import fs from 'fs';
import { newFs as fs } from 'fs'; // ES6语法, 将fs重命名为newFs, 命名冲突时常用
import { part } from fs;
import fs, { part } from fs;
// export
export default fs;
export const fs;
export function part;
export { part1, part2 };
export * from 'fs';
六、事件、宏任务微任务、进程线程
1. 事件绑定的几种方式
在 Javascript 中,事件绑定一共有3种方式:行内绑定、动态绑定、事件监听
1.行内绑定:[ 行内绑定中其 this 指向了全局 window 对象! ]
<input type=’button’ onclick=’display()’ />
2.动态绑定: [ 其事件处理中的 this 指向了当前正在操作的 dom 对象 ]
dom对象.事件 = 事件的处理程序(通常是一个匿名函数)
3.addEventListener 如果父元素 也有点击事件,这个会触发父元素的点击事件,需要阻止一下。
let tn = document.getElementById('btn');
tn.addEventListener('click', function(e) {
e.stopPropagation();//取消事件冒泡
})
4.onclick
let tn = document.getElementById('btn');
tn.onclick = function(e) {
//点击事件
e.stopPropagation(); // 阻止冒泡到父级的点击事件
}
2. 事件委托
优点:
1.可以大量节省内存占用,减少事件注册。比如ul上代理所有li的click事件就很不错。
2.可以实现当新增子对象时,无需再对其进行事件绑定,对于动态内容部分尤为合适
缺点:
事件代理的常用应用应该仅限于上述需求,如果把所有事件都用事件代理,可能会出现事件误判。即本不该被触发的事件被绑定上了事件。
1. 什么是事件委托
事件委托是利用事件冒泡,只指定一个事件处理程序来管理某一类型的所有事件。
2. 为什么要用事件委托
1)考虑一个 ul,在 li 的数量非常少的时候,为每一个 li 添加事件当然会使用for 循环;但是数量多
的时候这样做太浪费内存,长到上百上千上万的时候,为每个 li 添加事件
就会对页面性能产生很大的影响。
(2)给一个 ul 里面的几个 li 添加了事件, 但是如果动态又生成了 li 则刚生成的 li 不具备事件,
这时就需要用到委托。
3、作用:
1.性能要好
2.针对新创建的元素,直接可以拥有事件
4、事件委托原理
事件委托就是利用事件冒泡原理实现的!
事件冒泡:就是事件从最深节点开始,然后逐步向上传播事件;
例:页面上有一个节点树,div > ul > li > a
比如给最里面的 a 加一个 click 事件,那么事件就会一层一层的往外执行,
执行顺序 a > li > ul > div, 有这样一个机制,
当我们给最外层的div 添加点击事件,那么里面的 ul , li , a 做点击事件的时候,都会冒泡到
最外层的
div上,所以都会触发,这就是事件委托,委托他们父集代为执行事件;
5. 使用场景
• 为 DOM 中的很多元素绑定相同事件;
• 为 DOM 中尚不存在的元素绑定事件;
事件委托代码:
<body>
<ul id="box">
<li>1 事件事件事件</li>
<li>2 事件事件事件</li>
<li>3 事件事件事件</li>
<li>4 事件事件事件</li>
</ul>
<button click="addLi" id="btn">添加 LI</button>
<script>
let box = document.getElementById('box');
box.onclick = function(e) {
let event = e || window.event;
let target = event.target;
if(target.nodeName === 'LI') {
console.log(target.innerHTML)
}
}
let btn = document.getElementById('btn');
btn.onclick = function addLi() {
let tag = document.createElement('li');
tag.textContent = `${box.children.length + 1}事件事件`
box.appendChild(tag)
}
</script>
</body>
3. 事件流
事件流:
事件流分为两种,捕获事件流和冒泡事件流。
捕获事件流从根节点开始执行,一直往子节点查找执行,直到查找执行到目标节点。
冒泡事件流从目标节点开始执行,一直往父节点冒泡查找执行,直到查到到根节点。
DOM事件流分为三个阶段,一个是捕获节点,一个是处于目标节点阶段,一个是冒泡阶段。
**阻止冒泡**
div.addEventListener("click',function(e){
e.stopPropagation();
},true)
// 这样div的父元素就接收不到事件了
**阻止默认事件**
e.preventDefault();
4: 宏任务和微任务
图解:对象放在堆内存中,回调函数放在栈内存中
1. 宏任务:
用来保存待执行的宏任务(回调),比如:定时器回调、DOM 事件回调、ajax 回调
2. 微任务:
用来保存待执行的微任务(回调),比如:promise 的回调、MutationObserver 的回调。
3. JS 执行时会区别这 2 个队列
a. JS 引擎首先必须先执行所有的初始化同步任务代码
b. 每次准备取出第一个宏任务执行前,都要将所有的微任务一个一个取出来执行完,再执行宏任务。
5. 线程与进程的区别?
详情:www.ruanyifeng.com/blog/2013/0…
进程是分配内存的最小单位,线程是 CPU 调度的最小单位,进程可以包含多个线程 1.一个程序至少有一个进程,一个进程至少有一个线程.
2.线程的划分尺度小于进程,使得多线程程序的并发性高。
3.另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行 效率。
4.线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和 程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
5.从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统 并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和 线程的重要区别。
6. 事件循环
首先js是单线程的,那么就会出现一个问题就是阻塞问题,浏览器是怎么解决这个问题呢,比如说网络请求和settimeout,它是通过异步来做的,如果都用异步的话都会放在队列里面,异步队列有一个问题他没有优先级,所以为了更灵活增加了事件循环 事件循环: js是单线程它有 ,同步任务,和异步任务,一般是异步任务又分为微任务和宏任务 先执行同步任务队列执行完了,再执行微任务队列完了之后再执行宏任务,每 执行一次宏任务时会时时检测微任务队列有没有任务,如果有微任务 会先清空微任务队列,执行完了 再去执行下一个宏任务。所以一次循环,这就是事件循环
众所周知,JS是单线程的,那么如果出现多个任务,这些任务的执行顺序是怎么样的呢?这就不得不提一下事件循环
同步任务: 首先,我们用一个栈来表示主线程, 当有多个同步任务时,这些同步任务会依次入栈出栈,
如下图, 同步任务 1 先入栈,执行完之后,出栈,接着同步任务2入栈,依此类推
异步任务: 异步任务会在同步任务执行之后再去执行,那么如果异步任务代码在同步任务代码之前呢?在js机制里,存在一个队列,叫做任务队列,专门用来存放异步任务。也就是说,当异步任务出现的时候,会先将异步任务存放在任务队列中,当执行完所有的同步任务之后,再去调用任务队列中的异步任务
例如下图,现在存在两个同步任务,两个异步任务
js会先将同步任务 1 提至主线程,然后发现异步任务 1 和 2,则将异步任务 1 和 2 依次放入任务队列
然后同步任务 1 执行完之后出栈,又将同步任务 2 入栈执行
当同步任务2执行完之后,再从任务队列中提取异步任务执行
异步任务分类: js中,又将异步任务分为 宏任务 和 微任务,所以,上述任务队列也分为宏任务队列和微任务队列,那么,什么是宏任务,什么是微任务呢?
I/O、定时器、事件绑定、ajax等都是宏任务
Promise的then、catch、finally和process的nextTick都是微任务
注意:Promise的 then 等方法是微任务,而 Promise 中的代码是同步任务,并且,nextTick的执行顺序优先于Promise的then等方法,因为nextTick是直接告诉浏览器说要尽快执行,而不是放入队列
js中,微任务总是先于宏任务执行,也就是说,这三种任务的执行顺序是:同步任务>微任务>宏任务
例题
<script>
console.log(1);
setTimeout(function(){
console.log(2)
},0)
new Promise((resolve,reject)=>{
console.log(3)
resolve()
console.log(4)
}).then(()=>{
console.log(5)
})
console.log(6)
</script>
上述代码的执行结果是:1, 3, 4, 6, 5, 2
七、new 实例对象、原型链
1. new 操作符具体干了什么呢
如下代码,通过构造函数创建实例对象:
function Func(){
}
let func= new Func();
new 共经过了4个阶段:
1.创建一个空对象
let obj = new Object();
2. 让空对象的原型属性指向原型链,设置原型链
obj._proto_ = Func.prototype;
3. 绑定 this 值(让 Func 中的 this 指向 obj ,并执行 Func 的函数体。)
var result = Func.call(obj);
4. 返回新对象(判断Func的返回值类型:如果无返回值 或者 返回一个非对象值,
则将 obj 作为新对象返回;否则会将 result 作为新对象回。))
if (typeof(result) == "object"){
func = result;
} else {
func = obj;
}
2. js原型,原型链 ? 有什么特点?
每个对象都会在其内部初始化一个属性,就是prototype(原型),当我们访问一个对象的属性时,
如果这个对象内部不存在这个属性,那么他就会去prototype里找这个属性,这个prototype又会有
自己的prototype,于是就这样一直找下去,也就是我们平时所说的原型链的概念。
特点:
JavaScript对象是通过引用来传递的,我们创建的每个新对象实体中并没有一份属于自己的原型副本。
当我们修改原型时,与之相关的对象也会继承这一改变。
3. 作用域与作用域链
作用域:规定了如何查找变量,也就是确定当前执行代码对变量的访问权限。换句话说,作用域决定了代码区块中变量和其他资源的可见性。(全局作用域、函数作用域、块级作用域)
作用域链:从当前作用域开始一层层往上找某个变量,如果找到全局作用域还没找到,就放弃寻找 。这种层级关系就是作用域链。(由多个执行上下文的变量对象构成的链表就叫做作用域链,学习下面的内容之后再考虑这句话)
4. 创建一个对象的区别
let obj = Object.create(null);
let obj2 = {};
console.log(obj) // obj 的原型是 null 的原型,就是{}, 没有原型对象
console.log(obj2)// obj2 的原型 是 Object 的原型, 身上有 对象的原型属性
图解:
5. 获取原型的方法
第一种方法:proto, 但是,__proto__属性在IE浏览器中一直到IE11才被支持。
function F(){};
var foo = new F();
alert(foo.__proto__ == F.prototype); // true
第二种方法:constructor
constructor属性不是对象自己的属性,而是顺着原型链向上从原型对象中获取的。这个属性指向的是这个原型对象所对应的构造函数。而构造函数的 prototype 属性指向了原型对象, 所以这样我们就可以间接得到了。
function F(){};
var foo = new F();
alert(foo.constructor.prototype == F.prototype); // true
第三种: Object.getPrototypeOf(p) //推荐,规范写法
function F(){};
var foo = new F();
alert(Object.getPrototypeOf(foo) == F.prototype); // true
6. constructor
constructor 是 Object 类型的原型属性,它能够返回当前对象的构造器(类型函数)。利用该属性,可以检测复合类型数据的类型,如对象,数组和函数等。
var o = {};
var a = [];
if(o.constructor == Object){
console.log(`"o是对象"`)
}
if(a.constructor == Array){
console.log(`a是数组`)
}
八、深拷贝,浅拷贝
1. JSON 的了解?
1、json有两种格式就是对象和数组,它是javascript的一个子集。
2、对象在 js 中表示就是用 "{}" 包裹起来的,里面就是键值对。调用的时候就直接key就
可以调用它的值。
3、数组就是用 "[]" 包裹起来,取值的时候是通过它的索引值
4、常用的 json 就是将 json 转换成字符串用 JSON.stringiy,字符串转换成对象 JSON.parse,
目的就是替换掉麻烦的 xml
5、数据格式简单,便于读写,也是一种轻量级的数据交换格式,易于人阅读和编写,同时也易于
机器解析和生成
6. XML 类似 html 一样,处理比较麻烦,数据比较大。JSON 处理数据更方便,数据更清晰,数据
大小也更小
2. 深拷贝和浅拷贝
什么是深拷贝
在JS中,数据类型分为基本数据类型和引用数据类型两种,对于基本数据类型来说,它的值直 接存储在栈内存中,而对于引用类型来说,它在栈内存中仅仅存储了一个引用,而真正的数据 存储在堆内存中。
在对引用数据类型操作时,如果直接操作,改变的是堆内存里的数据,这样很容易影响到其他地方的引用,为了避免对其他这个问题。就需要用到深拷贝deepClone
JavaScript 中数组和对象自带的拷贝方法都是“首层浅拷贝”;
3. 对象的深拷贝和浅拷贝
一、浅拷贝的意思就是只复制引用(指针),而未复制真正的值。如果拷贝后的对象发生变化,原对象也会发生变化。
const originArray = [1,2,3,4,5];
const originObj = {a:'a',b:'b',c:[1,2,3],d:{dd:'dd'}};
const cloneArray = originArray;
const cloneObj = originObj;
console.log(cloneArray); // [1,2,3,4,5]
console.log(originObj); // {a:'a',b:'b',c:Array[3],d:{dd:'dd'}}
cloneArray.push(6);
cloneObj.a = {aa:'aa'};
console.log(cloneArray); // [1,2,3,4,5,6]
console.log(originArray); // [1,2,3,4,5,6]
console.log(cloneObj); // {a:{aa:'aa'},b:'b',c:Array[3],d:{dd:'dd'}}
console.log(originArray); // {a:{aa:'aa'},b:'b',c:Array[3],d:{dd:'dd'}}
上面的代码是最简单的利用 = 赋值操作符实现了一个浅拷贝,可以很清楚的看到,随着 cloneArray
和 cloneObj 改变,originArray 和 originObj 也随着发生了变化。
————————————————————————————————————————————————————————————————————
二、深拷贝就是对目标的完全拷贝,不像浅拷贝那样只是复制了一层引用,就连值也都复制了。
只要进行了深拷贝,它们老死不相往来,谁也不会影响谁。
目前实现深拷贝的方法不多,主要是两种:
1. 利用 JSON 对象中的 parse 和 stringify, JSON.stringify 实现的是深拷贝,但是对目标对象有要求(非 undefined,function);
2. 利用递归来实现每一层都重新创建对象并赋值
4. 深拷贝三种
a. 乞丐版的深拷贝 -- JSON.stringify() 和 JSON.parse()
var obj1 = {
a: 1,
b: 2,
c: 3
}
var objString = JSON.stringify(obj1);
var obj2 = JSON.parse(objString);
obj2.a = 5;
console.log(obj1.a); // 1
console.log(obj2.a); // 5
使用 JSON.stringify() 以及 JSON.parse() 它是不可以拷贝 undefined , function, RegExp 等等类型的
b. 递归拷贝
// 定义一个深拷贝函数 接收目标target参数
function deepClone(target) {
// 定义一个变量
let result;
// 如果当前需要深拷贝的是一个对象的话
if (typeof target === 'object') {
// 如果是一个数组的话
if (Array.isArray(target)) {
result = []; // 将result赋值为一个数组,并且执行遍历
for (let i in target) {
// 递归克隆数组中的每一项
result.push(deepClone(target[i]))
}
// 判断如果当前的值是null的话;直接赋值为null
} else if(target===null) {
result = null;
// 判断如果当前的值是一个RegExp对象的话,直接赋值
} else if(target.constructor===RegExp){
result = target;
}else {
// 否则是普通对象,直接for in循环,递归赋值对象的所有值
result = {};
for (let i in target) {
result[i] = deepClone(target[i]);
}
}
// 如果不是对象的话,就是基本数据类型,那么直接赋值
} else {
result = target;
}
// 返回最终结果
return result;
}
可以看一下效果
let obj1 = {
a: {
c: /a/,
d: undefined,
b: null
},
b: function () {
console.log(this.a)
},
c: [
{
a: 'c',
b: /b/,
c: undefined
},
'a',
3
]
}
let obj2 = deepClone(obj1);
console.log(obj2);
可以看到最终拷贝的结果是null、undefinde、function、RegExp等特殊的值也全部拷贝成功了,而且我们修改里边的值也不会有任何问题的
c. 使用 vue lodash
- 第一步 安装 lodash 插件
- 第二步 在项目中导入,官方推荐用下划线 "_" 命名
// 导入 lodash 这个插件
import _ from 'lodash'
// 挂载到 Vue 原型上
Vue.prototype._ = _
- 第三步 使用
this._.cloneDeep(value)
d. 解构赋值...
如果数组和对象是一维数据的时候,解构赋值... 就是深拷贝;
如果数组和对象是多维数据的时候,解构赋值... 就是浅拷贝;
let obj = {
name: 'xxl',
arr: [1 ,2 ,3 ,4]
}
let newObj = {...obj};
newObj.age = 18;
newObj.arr.push('xxl')
console.log(newObj, obj) // 浅拷贝
总结: 如果单独只是想使用deepClone深拷贝,推荐使用第二种方法,递归实现,而第三种方法,安装的lodash 插件,是一个方法库,里面包含了其他 js 的各种数据处理方法
九、本地存储和 http 缓存
1. 本地存储:cookie、localStorage、sessionStorage
1.cookie在浏览器和服务器端来回传递数据,而localStorage和sessionStorage不会自动把数据发送给服务器,仅会保存在本地。cookie会在浏览器请求头或者ajax请求头中发送cookie内容。
2.cookie可以设置过期日期,sessionStorage是会话级的数据,浏览器窗口关闭即清楚,localStorage是永久性的数据,一旦赋值,不管多长时间这值都是存在的,除非手动清除。
3.cookie的存储大小受限制,一般不超过4k,而localStorage和sessionStorage的存储大小一般不超过5M,大大提高了存储的体积。
4.sessionStorage不跨窗口,在另外一个窗口打开sessionStorage就不存在了,它只在当前窗口有效,而cookie和localStorage都是跨窗口的,即使浏览器的窗口关闭,这两个值还是存在的。
使用场景:
localStorage可以用来统计页面访问次数。
sessionStorage可以用来统计当前页面元素的点击次数。
cookie一般存储用户名密码相关信息,一般使用escape转义编码后存储。
一、有几种本地存储方法 cookie、localStorage、sessionStorage
二、请描述一下 cookies sessionStorage 和 localstorage 区别
相同点:数据都存储在本地电脑硬盘
域名限制,不同网站不能获取对方的值。
不同点:
1.存储大小
· cookie数据大小不能超过 4k。
· sessionStorage 和 localStorage 虽然也有存储大小的限制,但比 cookie 大得多,可以达到 5M 或
更大。
2.有效时间
· localStorage 存储持久数据,浏览器关闭后数据不丢失除非主动删除数据;
· sessionStorage 数据在当前浏览器窗口关闭后自动删除。
· cookie 设置的 cookie 过期时间之前一直有效,即使窗口或浏览器关闭
3. 数据与服务器之间的交互方式
· cookie 的数据会自动的传递到服务器,服务器端也可以写 cookie 到客户端
· sessionStorage 和 localStorage 不会自动把数据发给服务器,仅在本地保存。
4. 作用域不同:
sessionStorage:不在不同的浏览器窗口中共享,即使是同一个页面;
localstorage:在所有同源窗口中都是共享的;也就是说只要浏览器不关闭,数据仍然存在
cookie: 也是在所有同源窗口中都是共享的.也就是说只要浏览器不关闭,数据仍然存在
5. 存储值:
localStorage:
进行数据的存储操作使用localStorage.setItem(key,value),
取数据使用localStorage.getItem(key)
sessionStorage:
进行数据的存储操作使用sessionStorage.setItem(key,value),
取数据使用sessionStorage.getItem(key)
删除key,如sessionStorage.removeItem(key)。
清除所有:sessionStorage.clear();
cookie:
document.cookie = "name=Nicholas";
document.cookie = encodeURIComponent("name") + "=" +
encodeURIComponent("Nicholas");
2. http 缓存
1.关于缓存
什么是缓存? 把一些不需要重新获取的内容再重新获取一次
为什么需要缓存? 网络请求相比于 CPU 的计算和页面渲染是非常非常慢的。
哪些资源可以被缓存? 静态资源,比如 js css img。
2. 强制缓存
Cache-Control:
在 Response Headers 中。
控制强制缓存的逻辑。
例如 Cache-Control: max-age=3153600(单位是秒)
Cache-Control 有哪些值:
max-age:缓存最大过期时间。
no-cache:可以在客户端存储资源,每次都必须去服务端做新鲜度校验,来决定从服务端获取新的资源(200)还是使用客户端缓存(304)。
no-store:永远都不要在客户端存储资源,永远都去原始服务器去获取资源。
3. 协商缓存(对比缓存)
服务端缓存策略。
服务端判断客户端资源,是否和服务端资源一样。
一致则返回 304,否则返回 200 和最新的资源。
资源标识:
在 Response Headers 中,有两种。
Last-Modified:资源的最后修改时间。
Etag:资源的唯一标识(一个字符串,类似于人类的指纹)。
Last-Modified:
服务端拿到 if-Modified-Since 之后拿这个时间去和服务端资源最后修改时间做比较,如果一致则返回 304 ,不一致(也就是资源已经更新了)就返回 200 和新的资源及新的 Last-Modified。
Etag:
其实 Etag 和 Last-Modified 一样的,只不过 Etag 是服务端对资源按照一定方式(比如 contenthash)计算出来的唯一标识,就像人类指纹一样,传给客户端之后,客户端再传过来时候,服务端会将其与现在的资源计算出来的唯一标识做比较,一致则返回 304,不一致就返回 200 和新的资源及新的 Etag。
两者比较:
优先使用 Etag。
Last-Modified 只能精确到秒级。
如果资源被重复生成,而内容不变,则 Etag 更精确。
4. 综述
5. 三种刷新操作对 http 缓存的影响
正常操作:地址栏输入 url,跳转链接,前进后退等。强制缓存有效,协商缓存有效。
手动刷新:f5,点击刷新按钮,右键菜单刷新。强制缓存失效,协商缓存有效
强制刷新:ctrl + f5,shift+command+r。强制缓存失效,协商缓存失效
6. cookie 的属性
Cookie是由服务器端生成,浏览器会将Cookie的key/value保存到某个目录下的文本文件内,下次请求同一网站时就发送该Cookie给服务器。每个 cookie 除了 name 名称和 value 值这两个属性以外,常用属性还有:expires过期时间、path路径、domain域、以及secure安全、HttpOnly属性
1.
name: 字段为一个cookie的名称。
2.
value: 字段为一个cookie的值。
3.
**domain: 字段为可以访问此cookie的域名。 域,指定关联的WEB服务器或域。值是域名。这是对
path路径属性的一个延伸。如果我们想让
dev.mycompany.com能够访问bbs.mycompany.com设置的cookies,该怎么办? 我们可以把domain属性设置成“mycompany.com”,并把 path 属性设置成“/”。不能把cookies域属性设置成与设置它的服务器的所在域不同的值。设置的
cookie的domain只能为顶级域名或者二级域名或者三级域名本身,不能设置其他二级域名的cookie,否则cookie无法生成。顶级域名只能设置
domain为顶级域名,不能设置为二级域名或者三级域名,否则cookie无法生成。
二级域名能读取设置了
domain为顶级域名或者自身的cookie,不能读取其他二级域名domain的cookie。所以要想cookie在多个二级域名中共享,需要设置domain为顶级域名,这样就可以在所有二级域名里面或者到这个cookie的值了。顶级域名只能获取到
domain设置为顶级域名的cookie,其他domain设置为二级域名的无法获取。
4.
path: 字段为可以访问此cookie的页面路径。比如domain是abc.com,path是/test,那么只有/test路径下的页面可以读取此cookie。路径,指定与 cookie 关联的 WEB 页。
值可以是一个目录,或者是一个路径。
如果 /head/index.html 建立了一个cookie,那么在 /head/ 目录里的所有页面,以及该目录下面任何子目录里的页面都可以访问这个cookie。这就是说,在/head/stories/articles 里的任何页面都可以访问 /head/index.html 建立的cookie。
但是,如果 /zdnn/ 需要访问 /head/index.html 设置的 cookies,该怎么办?这时,我们要把 cookies 的 path 属性设置成 “/”。在指定路径的时候,凡是来自同一服务器,URL 里有相同路径的所有 WEB 页面都可以共享 cookies。现在看另一个例子:如果想让 /head/filters/ 和 /head/stories/ 共享 cookies,就要把 path 设成“/head”。
5.
expires/Max-Age: 字段为此 cookie 超时时间。若设置其值为一个时间,那么当到达此时间后,此 cookie 失效。不设置的话默认值是 Session,意思是cookie 会和 session 一起失效。当浏览器关闭(不是浏览器标签页,而是整个浏览器) 后,此 cookie 失效。过期时间,定cookie的生命期。
具体是值是过期日期。如果想让cookie的存在期限超过当前浏览器会话时间,就必须使用这个属性。当过了到期日期时,浏览器就可以删除cookie文件,没有任何影响。
注意:不设置的话默认值是Session,意思是cookie会和session一起失效。当浏览器关闭(不是浏览器标签页,而是整个浏览器) 后,此 cookie 失效。
Size: 字段 此 cookie 大小。
httponly: 若此属性为true,则只有在 http 请求头中会带有此 cookie 的信息,而不能通过 document.cookie 来访问此 cookie。如果在Cookie中设置了 ”HttpOnly” 属性,那么通过后台程序读取,JS 脚本将无法读取到 Cookie 信息,这样能有效的防止 XSS 攻击。
但是设置 HttpOnly 属性,Cookie 盗窃的威胁并没有彻底消除,因为 cookie 还是有可能传递的过程中被监听捕获后信息泄漏。
secure: 字段 设置是否只能通过 https 来传递此条 cookie安全,指定 cookie 的值通过网络如何在用户和 WEB 服务器之间传递。
这个属性的值或者是 “secure”,或者为空。
缺省情况下,该属性为空,也就是使用不安全的 HTTP 连接传递数据。
如果一个 cookie 标记为 secure,那么,它与 WEB 服务器之间就通过 HTTPS 或者其它安全协议传递数据。
不过,设置了 secure 属性不代表其他人不能看到你机器本地保存的 cookie。换句话说,把 cookie 设置为 secure,只保证 cookie 与 WEB 服务器之间的数据传输过程加密,而保存在本地的 cookie 文件并不加密。如果想让本地cookie 也加密,得自己加密数据。
7. localStorage设置数据实效性
封装函数实现localStorage存储数据实现数据具有实效性
1、设置localStorage数据时设置当前时间戳
/**
* 设置localStorage数据实效性
*/
setLocalStorage: function(key, value) {
const curTime = new Date().getTime(); // 时间撮
localStorage.setItem(key, JSON.stringify({ data: value, time: curTime }));
},
2. 获取localStorage数据时判断是否过期
/**
* 判断localStorage数据是否过期,过期删除,未过期正常返回 localStorage数据
*/
getLocalStorage: function(key, exp) {
const localData = localStorage.getItem(key);
const localDataObj = JSON.parse(localData);
const nowTime = new Date().getTime();
if (nowTime - localDataObj.time > exp) {
console.log("数据已过期");
// 删除localStorage已过期数据
localStorage.removeItem(key);
return 'localStorage数据已过期';
} else {
const data = JSON.parse(localDataObj.data);
return data;
}
},
3.应用
let val = '{"name":"setLocalValue"}';
this.setLocalStorage('info', val);
setTimeout(()=> {
//未过期localStorage
console.log(this.getLocalStorage("info",1000));
//输出 {name: "setLocalValue"}
},900)
this.setLocalStorage('info', val);
setTimeout(()=> {//过期localStorage
console.log(this.getLocalStorage("info",1000));
//localStorage数据已过期
},2000)
十一、axios
1. axios 的安装及引入
安装 axios
npm install axios
2. 请求方法封装 request/requestTypes.js
const requestTypes = {
GET:"get",
POST:"post",
PUT:"put",
DELETE:"delete",
``
}
export default requestTypes
3. 请求地址封装 request/apis.js
import requestTypes from "@/request/requestTypes";
import request from "@/request";
/**
* POST
**/
export function createResources(data) {
return request({
url: "/order/addResizeOrder",
method: requestTypes.POST,
data
});
}
/**
* GET
**/
export function productAttrsApi(data) {
return request({
url: "/product/sevice/cell",
method: requestTypes.GET,
data
});
}
4. axios 封装 request/index.js
import axios from "axios";
import Vue from "vue";
import Router from "@/router";
import { Message } from "view-design";
import { getToken, removeToken } from "@/utils";
const baseURL = "/api/";
Vue.prototype.$baseUrl = baseURL;
let _axios = axios.create({
baseURL,
timeout: 360 * 1000,
headers: { "Content-type": "application/json;charset=UTF-8" }
});
// 添加请求拦截器
_axios.interceptors.request.use(
config => {
// 在发送请求之前做些什么
if (getToken()) {
config.headers["token"] = getToken();
}
return config;
},
error => {
// 对请求错误做些什么
return Promise.reject(error);
}
);
// 添加响应拦截器
_axios.interceptors.response.use(
response => {
// 对响应数据做点什么,解析服务器返回的信息
let code = response.data.code;
let codeArrs = [
{ num: 401, text: '用户未登录' },
{ num: 403, text: '登录过期' },
{ num: 405, text: '权限不足' },
{ num: 406, text: '该用户已注销' },
{ num: 409, text: '未获取到当前用户信息' },
]
if (code === 200) {
let data = response.data.entity;
return data;
}
if (code === 400 || code === 500) {
Message.error({
content: response.data.message,
background: true,
duration: 2
});
let data = response.data;
return data;
}
let item = codeArrs.filter(ele => ele.num === code);
if (item.length > 0) {
Message.error({
content: item[0].text,
background: true,
duration: 3
});
// removeToken();
// Router.push("/verificate");
Router.replace({path: "/noPower"});
return false;
}
// 提示错误信息
let message = response.data.message;
Message.error({
content: message,
background: true,
duration: 3
});
setTimeout(() => {
// return Promise.reject(message || "Error");
}, 3000);
},
error => {
// 对响应错误做点什么
Message.error({
content: "网关错误 !",
background: true,
duration: 3
});
return Promise.reject(error);
}
);
export default _axios;
5. config 下的 index.js 设置跨域代理 baseUrl
ev: {
// Paths
assetsSubDirectory: "static",
assetsPublicPath: "/",
proxyTable: {
"/api": {
target: "http://192.168.10.221:9099",
changeOrigin: true,
timeout: 600 * 1000 * 10,
pathRewrite: {
"^/api": "/v1/oac/portalcenter/"
}
}
},
6. 在 main.js 中引入 axios
import "./request" // axios请求相关配置
7. 在组件中 使用 axios
import {createResources, resubmitDetail} from "@/request/apis.js";
// post 请求
createResources(params)
.then(res => {
})
.catch(() => {
});
// get 请求
resubmitDetail(params).then(res => {
});
8. axios 与 AJax 的区别
axios和ajax的区别:
axios是通过 Promise 实现对 ajax 技术的一种封装,就像 jquery 对 ajax 的封装一样 \
简单来说就是** ajax 技术实现了局部数据的刷新**,axios 实现了对 ajax 的封装,axios有的ajax都有,ajax有的axios不一定有,总结一句话就是axios是ajax,ajax不止axios。
9. axios 取消请求
取消请求总结:
1.先定义一个全局变更 cancel 为 null, 在请求的里添加一个配置的对象属性cancelToken,通过 axios实例对象 身上的 CancelToken 它返回一个 function, 把 这个 funciton 的值 赋值给 全局变量 cancel, 请求成功 把这个全局变更修改为 null.
2.在取消请求的时候,我们可以调用 cancel()
3.我们对一个按钮进行点击多次请求的时候,我们可以根据 cancel 值是否为 null, 等于 null 的话,说明没有在请求。 如果 cancel 值为一个 funciton, 则说明它正在请求,这里我们可以通过 cancel() 取消之前的请求。
let btns = document.querySelectorAll('button');
// 定义一个变量,判断是否请求成功,null 表示未请求成功,如果是个 Function 表示请求成功
let cancel = null;
btns[0].onclick = () => {
if (cancel !== null) {
// 说明正在请求,要把该请求给取消
cancel();
}
axios({
method: 'GET',
url: 'http://localhost:3000/posts',
// 添加配置对象的属性
cancelToken: new axios.CancelToken(function (c) {
// 赋值给 cancel
cancel = c;
console.log(c instanceof Function, c)
})
}).then(res => {
console.log('请求成功')
// 请求成功了把值变成初始化状态
cancel = null;
}).catch(reason => {
console.log('请求失败')
})
}
btns[1].onclick = () => {
cancel();
}
btns[2].onclick = () => {
axios({
method: 'GET',
url: 'http://localhost:3000/comments',
}).then(res => {
}).catch(reason => {
})
}
10.Axios 二次封装(超详细)
- 创建 http.js 文件模块进行 axios 封装
import axios from "axios";
//带三方类库
import qs from 'qs'
// 配置不同环境下,调用不同接口
switch(process.env.NODE_ENV){
// 生产环境,部署到服务器上的环境
case 'production':
axios.defaults.baseURL='http://api.zhengqinan.cn';
break;
//设置测式环境的接口地址
case 'text':
axios.defaults.baseURL='http://api.zhengqinantext.cn';
break;
//开发环境接口地址
default:
axios.defaults.baseURL='http://api.kaifa.cn'
}
/**
* 设置超时时间和跨域是否允许携带凭证
*/
axios.defaults.timeout=10000 //设置十秒
axios.defaults.withCredentials=true ;//例如:登录校验session和cookie
/**
* 设置请求数据参数传递的格式,默认是json格式,但是在登录校验中后台一般设置请求格式:x-www-form-urlencoded(name=xxx,age=xxx)
* 看服务器要求什么格式
*/
axios.defaults.headers['Content-Type']='application/x-www-form-urlencoded' //声明请求格式
axios.defaults.transformRequest=data=>qs.stringify(data) //qs是第三方库,转换为x-www-form-urlencoded
/**
* 设置请求拦截器:----在项目中发请求(请求没有发出去)可以做一些事情
* 客户端->[请求拦截器]->服务器端
* token校验(JWT):接收到服务器的token,存储到vuex/本地存储中,每次向服务器发送请求,我们应该把token带上
* config :发起请求的请求配置项
*/
axios.interceptors.request.use(config=>{
let token=localStorage.getItem('token')
token && (config.headers.Authoriztion=token)
return config
},error=>{
return Promise.reject(error)
})
/**
* 设置响应拦截器
* 服务器端返回信息->[响应拦截器]->客户端js获取到信息
* response中包含属性:
* data:相应数据,status:响应状态码,statusText:响应状态信息,headers:响应头,config:响应提供的配置信息,request
*/
axios.interceptors.response.use(response=>{
return response.data //将主体内容返回 axios.get().then(result=>{拿到的就是响应主体})
},error=>{
let { response}=error
// 如果有返回结果
if(response){
switch(response.status){
//这里面根据公司需求进行写
case 404:
//进行错误跳转之类
break;
}
}else{
//服务器没有返回结果 分两种情况 断网 服务器崩了
if(!window.navigator.onLine){
//断网处理:跳转到断网页面
return
}
return Promise.reject(error)
}
})
2.这里可以分页面进行创建接口请求文件, 使用 post 和get进行请求
import axiosHttp from './http.js'
export const getuserinfor=(params)=>{
return axiosHttp({
method:'get',
url:'user/xxxx',
params
})
};
export const login=(data)=>{
return axiosHttp({
method:'post',
url:'login/xxxxx',
data,
})
}
3.在代码中进行使用 先导入模块,再进行使用
<template></template>
<script>
import { login } from "../api/user.js";
export default {
data() {
return {};
},
methods: {
async getlogin() {
let params = {
emile: 123,
name: "zhangsna",
};
let data = await login(params);
},
},
};
</script>