ES6 是 2015 年推出的一个新的版本、这个版本相对于 ES5 的语法做了很多的优化、例如:
新增了let、 const, let 和 const具有块级作用域,不存在变量提升的问题。
新增了箭头函数,简化了定义函数的写法,同时 可以巧用箭头函数的 this、(注意箭头函数本身没有 this,它的 this 取决于外部的环境)
新增了promise, 解决了回调地狱的问题
新增了模块化、利用 import 、export 来实现导入、导出。
新增了解构赋值, ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构 (Destructuring)。
新增了class 类的概念,它类似于对象。
扩展运算符…
2.6 Object 和 Symbol
(1) Object对象
支持简写:同名属性K-V可以省略一个、函数声明可以省略function;支持属性名表达式、函数名表达式。(注意:以上2个——表达式和简写不能同时使用)。
对象的方法的name属性返回方法名,但有几个例外情况要小心。新增了Object方法
Object.is()——用于解决 == 和 === 的部分兼容问题
Object.assign()——将src的所有可枚举对象属性复制到dest对象上(浅复制)
Object.setPrototypeOf()、Object.getPrototypeOf() (Object.__proto属性)
Object.entries()、Object.keys()、Object.values()
ES6中5种遍历对象属性的方法
for-in——自身和继承的可枚举属性(除Symbol)
Object.keys()——自身非继承的可枚举属性(除Symbol)
Object.getOwnPropertyNames()——自身所有属性键名(包括不可枚举、除Symbol)
Object.getOwnPropertySymbols()——自身的所有 Symbol 属性的键名
Reflect.ownKeys()——自身的所有键名
(2)Symbol类型
ES5以前,对象属性都只能是字符串,容易造成重命名导致的冲突。Symbol提供了一种机制,可以保存 属性名是独一无二的。Symbol类型的使用注意:1)创建是调用函数,而不是new关键字 2)Symbol类 型的属性不会被for-*、Object.keys()、Object.getPropertyNames()返回,可以用后面两种方法遍历。
数据结构Set和Map
Set是一种类似数组的数据结构,区别在于其存储的成员都是不重复的,由此带来了它的一个应用就是:去重。Set通过new关键字实例化,入参可以是数组or类数组的对象。
值得注意的是:在Set中,只能存储一个NaN,这说明在Set数据结构中,NaN等于NaN。
Set实例的方法:操作方法add()、delete()、has()和clear();遍历方法:keys()、values()、entries()和forEach();扩展运算符…、数组方法map()、filter()方法也可以用于Set结构。由此它可以很方便的实现数组的交、并、差集。
WeakSet类似于Set,主要区别在于1.成员只能是对象类型;2.对象都是弱引用(如果其他对象都不再引用该对象,垃圾回收机制会自动回收该对象所占的内存,不可预测何时会发生,故WeakSet不可被遍历)
JavaScript对象Object都是键值K-V对的集合,但K取值只能是字符串和Symbol,Map也是K-V的集合,然而其K可以取任意类型。如果需要键值对的集合,Map比Object更适合。Map通过new关键字实例化。
Map实例的方法:set()、get()、has()、delete()和clear();遍历方法同Set。
Map与其它数据结构的互相转换:Map <—> 数组| Map <—> 对象| Map <—> JSON。
WeakMap类似于Map,主要区别在于:1.只接受对象作为键名;2.键名所指向的对象不计入垃圾回收机制
Iterator
ES6之前在JS中只有Array和对象可以表示“集合”这种数据结构,ES6中增加了:Set和Map。由此,四种之间互相组合又可以定义新的数据结构。这些新定义的数据结构如何访问呢?遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作。遍历器对象本质上是一个指针对象。
只要为某个数据结构部署 了Iterator接口,则可以称此数据结构是可遍历的。iterator属性部署在Symbol上。如下对象默认部署了Iterator结口:Array Set Map String等。部署iterator结构的要点:1)在Symbol.iterator上部署;2)必须包含next()函数。默认调用iterator接口的场景:解构赋值、…扩展运算符、yeild* 。for-of循环内部调用的即是调用数据机构内部的Symbol.iterator方法。
for-in和for-of循环
for-in用于遍历对象属性,对象自身和继承的可枚举属性(不可遍历Symbol属性)
for-of循环是一种遍历所有数据机构的统一方法。实现原理是数据结构上部署的Symbol.iterator属性。
var ——ES5 变量声明方式
在变量未赋值时,变量undefined(为使用声明变量时也为undefined)
作用域——var的作用域为方法作用域;只要在方法内定义了,整个方法内的定义变量后的代码都可以使用
let——ES6变量声明方式
在变量为声明前直接使用会报错
作用域——let为块作用域——通常let比var 范围要小
let禁止重复声明变量,否则会报错;var可以重复声明
const——ES6变量声明方式
const为常量声明方式;声明变量时必须初始化,在后面出现的代码中不能再修改该常量的值
const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址不得改动
暂时性死区的本质就是,只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。
扩展:
let 、const与暂时性死区
let
或 const
声明的变量拥有暂时性死区(TDZ):当进入它的作用域,它不能被访问(获取或设置)直到执行到达声明。
首先看看不具有暂时性死区的 var
:
var
变量的作用域(包围它的函数),立即为它创建(绑定)存储空间。变量会立即被初始化并赋值为 undefined
。通过 let
声明的变量拥有暂时性死区,生命周期如下:
let
变量的作用域(包围它的语法块),立即为它创建(绑定)存储空间。此时变量仍是未初始化的。ReferenceError
。undefined
。const
工作方式与 let
类似,但是定义的时候必须赋值并且不能改变。
在 TDZ 内部,如果获取或设置变量将抛出异常:
if (true) { // enter new scope, TDZ starts
// Uninitialized binding for `tmp` is created
tmp = 'abc'; // ReferenceError
console.log(tmp); // ReferenceError
let tmp; // TDZ ends, `tmp` is initialized with `undefined`
console.log(tmp); // undefined
tmp = 123;
console.log(tmp); // 123
}
下面的示例将演示死区(dead zone)是真正短暂的(基于时间)和不受空间条件限制(基于位置)
if (true) { // enter new scope, TDZ starts
const func = function () {
console.log(myVar); // OK!
};
// Here we are within the TDZ and
// accessing `myVar` would cause a `ReferenceError`
let myVar = 3; // TDZ ends
func(); // called outside TDZ
}
typeof
与暂时性死区
变量在暂时性死区无法被访问,所以无法对它使用 typeof
:
if (true) {
console.log(typeof tmp); // ReferenceError
let tmp;
}
Number,String,Boolean,null,undefined,symbol,bigint(后两个为ES6新增)
object,function(proto Function.prototype)
object:普通对象,数组对象,正则对象,日期对象,Math数学函数对象。
基本数据类型是直接存储在栈中的简单数据段,占据空间小、大小固定,属于被频繁使用的数据。栈是存储基本类型值和执行代码的空间。
引用数据类型是存储在堆内存中,占据空间大、大小不固定。引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址,当解释器寻找引用值时,会检索其在栈中的地址,取得地址后从堆中获得实体。
然后判断数据类型的方法一般可以通过:typeof、instanceof、constructor、toString四种常用方法
不同类型的优缺点 | typeof | instanceof | constructor | Object.prototype.toString.call |
---|---|---|---|---|
优点 | 使用简单 | 能检测出引用 类型 | 基本能检测所有 的类型(除了 null 和 undefined) | constructor 易被 修改,也不能跨 iframe |
缺点 | 只能检测 出基本类 型(出 null) | 不能检测出基 本类型,且不 能跨 iframe | constructor 易被 修改,也不能跨 iframe | IE6 下,undefined 和 null 均为 Object |
作用:Object.assign 可以实现对象的合并。
语法:Object.assign(target, ...sources)
解析:
https://www.jianshu.com/p/f9ec860ecd81
示例
下方提供了一个数组,如果我们想将其中的每一个元素翻倍,我们可以使用map
和forEach
来达到目的。
let arr = [1, 2, 3, 4, 5];
ForEach
注意,forEach是不会返回有意义的值的。
我们在回调函数中直接修改arr的值。
arr.forEach((num, index) => {
return arr[index] = num * 2;}
);
执行结果如下:
// arr = [2, 4, 6, 8, 10]
Map
let doubled = arr.map(num => {
return num * 2;
});
执行结果如下:
// doubled = [2, 4, 6, 8, 10]
哪个更好呢?
取决于你想要做什么。
forEach适合于你并不打算改变数据的时候,而只是想用数据做一些事情 – 比如存入数据库或则打印出来。
let arr = ['a', 'b', 'c', 'd'];
arr.forEach((letter) => {
console.log(letter);
});
// a
// b
// c
// d
map() 适用于你要改变数据值的时候。不仅仅在于它更快,而且返回一个新的数组。这样的优点在于你可以使用复合(composition)(map(), filter(), reduce()等组合使用)来玩出更多的花样。
let arr = [1, 2, 3, 4, 5];
let arr2 = arr.map(num => num * 2).filter(num => num > 5);
// arr2 = [6, 8, 10]
我们首先使用map将每一个元素乘以2,然后紧接着筛选出那些大于5的元素。最终结果赋值给arr2。
核心要点
for…of…: 它是es6新增的一个遍历方法,但只限于迭代器(iterator), 所以普通的对象用for…of遍历是会报错的。
可迭代的对象:包括Array, Map, Set, String, TypedArray, 函数的arguments对象,NodeList 对象
优点:
缺点:
JavaScript是单线程语言,所以执行肯定是按顺序执行。但是并不是逐行的分析和执行,而是一段一段地分析执行,会先进行编译阶段然后才是执行阶段。在编译阶段阶段,代码真正执行前的几毫秒,会检测到所有的变量和函数声明,所有这些函数和变量声明都被添加到名为Lexical Environment的JavaScript数据结构内的内存中。所以这些变量和函数能在它们真正被声明之前使用。
用var声名的变量在声明之前的地方就能被使用的现象称为变量提升,值为undefined,es6新增的let和const解决了变量提升的这一问题,在let和const声明之前会存在暂时性死区,如果在声明之前使用会直接报错。
概念: 作用域就是一个独立的地盘,让变量不会外泄、暴露出去。也就是说作用域最大的用处就是隔离变量,不同作用域下同名变量不会有冲突。
ES6 之前 JavaScript 没有块级作用域,只有全局作用域和函数作用域。ES6 的到来,为我们提供了‘块级作用域’,可通过新增命令 let 和 const 来体现。
扩展:
var ——ES5 变量声明方式
let——ES6变量声明方式
const——ES6变量声明方式
const为常量声明方式;声明变量时必须初始化,在后面出现的代码中不能再修改该常量的值
const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址不得改动
总结
综上所述,数据量比较小,并且需要频繁的使用Map存储数据的时候,推荐使用ArrayMap。 而数据量比较大的 时候,则推荐使用HashMap。
函数和对象的关系
函数是对象,对象都是通过函数创建的
函数与对象并不是简单的包含与被包含的关系
原型的类别
显示原型:prototype,是每个函数 function 独有的属性
隐式原型:_proto_,是每个对象都具有的属性。
原型和原型链
原型:一个函数可以看成一个类,原型是所有类都有的一个属性,原型的作用就是给这个类的一个对象都添加一个统一的方法。
原型链:每个对象都有一个_proto_, 它指向它的 prototype 原型对象;它的 prototype 原型对象又有一个 _proto _,指向它的 prototype 原型对象,就这样层层向上最终找到顶级对象 Object 的 prototype,这个查询路径就是原型链。
let a = [1,2,3];
let b = [4,6];
console.log(a.concat(b));
var ages = [4, 12, 16, 20];
function checkAdult(age) {
return age >= document.getElementById("ageToCheck").value;
}
function myFunction() {
document.getElementById("demo").innerHTML = ages.find(checkAdult);
}
var ages = [3, 10, 18, 20];
function checkAdult(age) {
return age >= 18;
}
function myFunction() {
document.getElementById("demo").innerHTML = ages.findIndex(checkAdult);
let site = ['runoob', 'google', 'taobao'];
site.includes('runoob');
// true
site.includes('baidu');
// false
var fruits = ["Banana", "Orange", "Apple", "Mango"];
var a = fruits.indexOf("Apple");//2
var fruits = ["Banana", "Orange", "Apple", "Mango"];
var energy = fruits.join();//Banana,Orange,Apple,Mango
var fruits = ["Banana", "Orange", "Apple", "Mango"];
var energy = fruits.join(" and ");//Banana and Orange and Apple and Mango
var fruits = ["Banana", "Orange", "Apple", "Mango"];
fruits.pop();//Banana,Orange,Apple
8 . push() 方法将一个或多个元素添加到数组的末尾,并返回该数组的新长度。
var fruits = ["Banana", "Orange", "Apple", "Mango"];
fruits.push("Kiwi")//Banana,Orange,Apple,Mango,Kiwi
var fruits = ["Banana", "Orange", "Apple", "Mango"];
fruits.shift()//Orange,Apple,Mango
var fruits = ["Banana", "Orange", "Apple", "Mango"];
fruits.unshift("Lemon","Pineapple");//Lemon,Pineapple,Banana,Orange,Apple,Mango
var fruits = ["Banana", "Orange", "Apple", "Mango"];
//在索引为2位置开始删除0个元素,同时添加"Lemon","Kiwi"
fruits.splice(2,0,"Lemon","Kiwi");//Banana,Orange,Lemon,Kiwi,Apple,Mango
slice() 方法同上,但不会改变原数组
slice() 方法可从已有的数组中返回选定的元素。
slice() 方法可提取字符串的某个部分,并以新的字符串返回被提取的部分。
var fruits = ["Banana", "Orange", "Lemon", "Apple", "Mango"];
var citrus = fruits.slice(1,3);//Orange,Lemon
var fruits = ["Banana", "Orange", "Apple", "Mango"];
fruits.reverse();//Mango,Apple,Orange,Banana
var fruits = ["Banana", "Orange", "Apple", "Mango"];
fruits.sort();//Apple,Banana,Mango,Orange
every() 方法测试一个数组内的所有元素是否都能通过某个指定函数的测试。它返回一个布尔值。
every() 方法用于检测数组所有元素是否都符合指定条件(通过函数提供)。
every() 方法使用指定函数检测数组中的所有元素:
var ages = [32, 33, 16, 40];
function checkAdult(age) {
return age >= 18;
}
function myFunction() {
document.getElementById("demo").innerHTML = ages.every(checkAdult);//false
}
var ages = [32, 33, 16, 40];
function checkAdult(age) {
return age >= 18;
}
function myFunction() {
document.getElementById("demo").innerHTML = ages.filter(checkAdult);//32,33,40
}
var numbers = [4, 9, 16, 25];
function a(num,index) {
console.log(num);
console.log("---------------");
console.log(index);
}
numbers.forEach(a)
输出:
4
---------------
0
9
---------------
1
16
---------------
2
25
---------------
3
some() 方法用于检测数组中的元素是否满足指定条件(函数提供)。
some() 方法会依次执行数组的每个元素:
注意: some() 不会对空数组进行检测。
注意: some() 不会改变原始数组。
var ages = [3, 10, 18, 20];
function checkAdult(age) {
return age >= 18;
}
function myFunction() {
document.getElementById("demo").innerHTML = ages.some(checkAdult);//true
}
const array1 = [1, 4, 9, 16];
// pass a function to map
const map1 = array1.map(x => x * 2);
console.log(map1);
// expected output: Array [2, 8, 18, 32]
function unique (arr) {
return Array.from(new Set(arr))
}
var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
console.log(unique(arr))
//[1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {}, {}]
function unique(arr){
for(var i=0; i<arr.length; i++){
for(var j=i+1; j<arr.length; j++){
if(arr[i]==arr[j]){ //第一个等同于第二个,splice方法删除第二个
arr.splice(j,1);
j--;
}
}
}
return arr;
}
var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
console.log(unique(arr))
//[1, "true", 15, false, undefined, NaN, NaN, "NaN", "a", {…}, {…}] //NaN和{}没有去重,两个null直接消失了
function unique(arr) {
if (!Array.isArray(arr)) {
console.log('type error!')
return
}
var array = [];
for (var i = 0; i < arr.length; i++) {
if (array .indexOf(arr[i]) === -1) {
array .push(arr[i])
}
}
return array;
}
var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
console.log(unique(arr))
// [1, "true", true, 15, false, undefined, null, NaN, NaN, "NaN", 0, "a", {…}, {…}] //NaN、{}没有去重
function unique(arr) {
if (!Array.isArray(arr)) {
console.log('type error!')
return
}
var array =[];
for(var i = 0; i < arr.length; i++) {
if( !array.includes( arr[i]) ) {//includes 检测数组是否有某个值
array.push(arr[i]);
}
}
return array
}
var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
console.log(unique(arr))
//[1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {…}, {…}] //{}没有去重
利用filter + hasOwnProperty 进行去重,创建一个新对象obj,利用filter遍历数组中的每个元素,检测obj中是否有这个元素,若没有,则加入,并且另filter 返回该元素,若遍历到重复元素,则不返回。
function unique(arr) {
var obj = {};
return arr.filter(function(item, index, arr){
return obj.hasOwnProperty(typeof item + item) ? false : (obj[typeof item + item] = true)
})
}
var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
console.log(unique(arr))
//[1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {…}] //所有的都去重了
undefined 表示一个变量自然的、最原始的状态值,而 null 则表示一个变量被人为的设置为空对象,而不是原始状态。所以,在实际使用过程中,为了保证变量所代表的语义,不要对一个变量显式的赋值 undefined,当需要释放一个对象时,直接赋值为 null 即可。
解析:
undefined 的字面意思就是:未定义的值 。这个值的语义是,希望表示一个变量最原始的状态,而非人为操作的结果 。 这种原始状态会在以下 4 种场景中出现:
因此,undefined 一般都来自于某个表达式最原始的状态值,不是人为操作的结果。当然,你也可以手动给一个变量赋值 undefined,但这样做没有意义,因为一个变量不赋值就是 undefined 。
null 的字面意思是:空值 。这个值的语义是,希望表示 一个对象被人为的重置为空对象,而非一个变量最原始的状态 。 在内存里的表示就是,栈中的变量没有指向堆中的内存对象
null 有属于自己的类型 Null,而不属于Object类型,typeof 之所以会判定为 Object 类型,是因为JavaScript 数据类型在底层都是以二进制的形式表示的,二进制的前三位为 0 会被 typeof 判断为对象类型,而 null 的二进制位恰好都是 0 ,因此,null 被误判断为 Object 类型。
区别
本质:类数组是简单对象,它的原型关系与数组不同。类数组转换为数组
转换方法
Array.from()
Array.prototype.slice.call()
Array.prototype.forEach()
进行属性遍历并组成新的数组https://www.jianshu.com/p/4efa7675834c
Set
ES6 新增的一种新的的数据结构,类似于数组,但成员是唯一且无序的,没有重复的值。
成员不能重复;
只有键值,没有键名,有点类似数组;
可以遍历,方法有 add、delete、has
add: 新增,相当于array里的push
delete: 存在即删除集合中的value
has: 判断集合中是否存在value
clear: 清空集合
let s = new Set()
s.add(1).add(2).add(1) // Set{1, 2}
s.has(1) // true
s.has(3) // false
s.delete(1)
s.has(1) // false
s.clean() // Set{}
Set 结构可以利用 Array.from
或者 扩展运算符 转化为数组
let s = new Set([1, 2, 3])
// Array.from
let arr = Array.from(s)
console.log(arr) // [1, 2, 3]
// 扩展运算符
console.log([...s]) // [1, 2, 3]
WeakSet
add、delete、has
;Map
Map
对象保存键值对,并且能够记住键的原始插入顺序。任何值(对象或者基本类型)都可以作为一个键或一个值。const map1 = new Map();
map1.set('a', 1);
map1.set('b', 2);
map1.set('c', 3);
console.log(map1.get('a'));
// expected output: 1
map1.set('a', 97);
console.log(map1.get('a'));
// expected output: 97
console.log(map1.size);
// expected output: 3
map1.delete('b');
console.log(map1.size);
// expected output: 2
WeakMap
get、set、has、delete
;内存泄漏: 由于疏忽或错误造成未能释放已经不再使用的内存/不再用到的内存没有及时释放
闭包 --不用的时候让元素 = null;
未被清空的定时器—在不用的时候及时清除定时器;
未被销毁的事件监听–在不使用的时候取消事件监听;
DOM 引用 —让元素 = null;
同步
异步
微任务 执行时机比 宏任务 早
宏任务:setTimeout,setInterval,DOM事件,AJAX请求
微任务:Promise,async/await,process.nextTick()
微任务 > DOM渲染 > 宏任务
见到 await 必须等await执行完,才能执行之后的代码
async await也是异步编程的一种解决方案,他遵循的是Generator 函数的语法糖,他拥有内置执行器,不需要额外的调用直接会自动执行并输出结果,它返回的是一个Promise对象。
两者的区别
function sleep (time) {
return new Promise((resolve) => setTimeout(resolve, time));
}
// 用法
sleep(500).then(() => {
// 这里写sleep之后需要去做的事情
})
不要忘了开源的力量
const sleep = require("sleep")
const t1 = +new Date()
sleep.msleep(3000)
const t2 = +new Date()
console.log(t2 - t1)
优点:能够实现更加精细的时间精确度,而且看起来就是真的 sleep 函数,清晰直白。
for (var i = 0; i< 10; i++){
setTimeout(() => {
console.log(i);
}, 0)
} //输出10个10,而不是输出1到10
期望:输出1到10
为什么无法输出1到十
在上面的代码中,for循环是同步代码,setTimeout是异步代码。遇到这种既包含同步又包含异步的情况,JavaScript依旧按照从上到下的顺序执行同步代码,并将异步代码插入任务队列。setTimeout的第二个参数则是把执行代码(console.log(i))添加到任务队列需等待的毫秒数,但等待的时间是相对主程序完毕的时间计算的,也就是说,在执行到setTimeout函数时会等待一段时间,再将当前任务插入任务队列。
最后,当执行完同步代码,js引擎就会去执行任务队列中的异步代码。这时候任务队列中就会有十个console.log(i)。我们知道,在每次循环中将setTimeout里面的代码“console.log(i)”放入任务队列时,i的值都是不一样的。但JavaScript引擎开始执行任务队列中的代码时,会开始在当前的作用域中开始找变量i,但是当前作用域中并没有对变量i进行定义。这个时候就会在创造该函数的作用域中寻找i。创建该函数的作用域就是全局作用域,这个时候就找到了for循环中的变量i,这时的i是全局变量,并且值已经确定:10。十个console.log“共享”i的值。这就是作用域链的问题。
解决方法
for (let i = 0; i< 10; i++){
setTimeout(() => {
console.log(i)
}, 1000);
}
最优解决方案,利用
let
形成块级作用域
1、普通函数中的this
谁调用了函数或者方法,那么这个函数或者对象中的this就指向谁
let getThis = function () {
console.log(this);
}
let obj={
name:"Jack",
getThis:function(){
console.log(this);
}
}
//getThis()方法是由window在全局作用域中调用的,所以this指向调用该方法的对象,即window
getThis();//window
//此处的getThis()方法是obj这个对象调用的,所以this指向obj
obj.getThis();//obj
let obj = {
getThis: function () {
return function () {
console.log(this);
}
}
}
obj.getThis()(); //window
上面代码中,getThis()方法是由obj调用,但是obj.getThis()返回的是一个匿名函数,而匿名函数中的this指向window,所以打印出window。 如果想在上述代码中使this指向调用该方法的对象,可以提前把this传值给另外一个变量(_this或者that):
let obj = {
getThis: function () {
//提前保存this指向
let _this=this
return function () {
console.log(_this);
}
}
}
obj.getThis()(); //obj
3.箭头函数中的this
例1:首先,距离箭头函数最近的是getThis(){},与该函数平级的执行上下文是obj中的执行上下文,箭头函数中的this就是下注释代码处的this,即obj。
let obj = {
//此处的this即是箭头函数中的this
getThis: function () {
return ()=> {
console.log(this);
}
}
}
obj.getThis()(); //{ getThis: [Function: getThis] }
例2:该段代码中存在两个箭头函数,this找不到对应的function(){},所以一直往上找直到指向window。
//代码中有两个箭头函数,由于找不到对应的function,所以this会指向window对象。
let obj = {
getThis: ()=> {
return ()=> {
console.log(this);
}
}
}
obj.getThis()(); //window
1. charAt() 方法从一个字符串中返回指定索引的字符。
var str = "HELLO WORLD";
var n = str.charAt(2)//L
2. concat() 方法将一个或多个字符串与原字符串连接合并,形成一个新的字符串并返回。
var str1 = "Hello ";
var str2 = "world!";
var n = str1.concat(str2);//Hello world!
3. includes() 方法用于判断一个字符串是否包含在另一个字符串中,根据情况返回 true 或 false。
var str = "Hello world, welcome to the Runoob。";
var n = str.includes("Runoob");//true
4. indexOf() 方法返回调用它的 String 对象中第一次出现的指定值的索引,从 fromIndex 处进行搜索。如果未找到该值,则返回 -1。 match() 方法检索返回一个字符串匹配正则表达式的的结果。
var str="Hello world, welcome to the universe.";
var n=str.indexOf("welcome");//13
5. padStart() 方法用另一个字符串填充当前字符串(重复,如果需要的话),以便产生的字符串达到给定的 长度。填充从当前字符串的开始(左侧)应用的。 (常用于时间补 0)
'abc'.padStart(10); // " abc"
'abc'.padStart(10, "foo"); // "foofoofabc"
'abc'.padStart(6,"123465"); // "123abc"
'abc'.padStart(8, "0"); // "00000abc"
'abc'.padStart(1); // "abc"
6. replace() 方法返回一个由替换值( replacement )替换一些或所有匹配的模式( pattern )后的新 字符串。模式可以是一个字符串或者一个正则表达式,替换值可以是一个字符串或者一个每次匹配都要 调用的回调函数。 原字符串不会改变。 slice() 方法提取某个字符串的一部分,并返回一个新的字符串,且不会改动原字符串。
在本例中,我们将执行一次替换,当第一个 "Microsoft" 被找到,它就被替换为 "Runoob":
var str="Visit Microsoft! Visit Microsoft!";
var n=str.replace("Microsoft","Runoob");//Visit Runoob!Visit Microsoft!
7. split() 方法使用指定的分隔符字符串将一个 String 对象分割成字符串数组,以将字符串分隔为 子字符串,以确定每个拆分的位置。 substr() 方法返回一个字符串中从指定位置开始到指定字符数的字符。trim() 方法会从一个字符串的两端删除空白字符。在这个上下文中的空白字符是所有的空白字符 (space, tab, no-break space 等) 以及所有行终止符字符(如 LF,CR)。
var str="How are you doing today?";
var n=str.split(" ");//How,are,you,doing,today?
var str="Hello world!";
var n=str.substr(2,3)//llo
var str = " Runoob ";
alert(str.trim());//Runoob
闭包(closure)指有权访问另一个函数作用域中变量的函数。简单理解就是 ,一个作用域可以访问另外一个函数内部的局部变量。
因为外层函数拿不到内层函数的值,所以可以在一个函数里面再定义一个函数,用这个函数获取到外层函数中的局部变量,那么只要将内部的函数作为返回值,就可以在函数的外部读取到函数内部的变量。
function fn() {
var num = 10
function fun() {
console.log(num)
}
return fun
}
var f = fn()
f()
作用: 延长变量作用域、在函数的外部可以访问函数内部的局部变量,容易造成内存泄露,因为闭包中的局部变量永远不会被回收
闭包的特点:
让外部访问函数内部变量成为可能;
可以避免使用全局变量,防止全局变量污染;
可以让局部变量常驻在内存中;
会造成内存泄漏(有一块内存空间被长期占用,而不被释放)
原型链继承
原型链继承的优点
原型链继承的缺点
鉴于这些缺点,实践中很少会单独使用原型链继承。
借用构造函数继承(对象冒充)
借用构造函数的优点
借用构造函数的缺点
寄生组合式继承
顾名思义,寄生式 + 组合式。它是寄生式继承的加强版。这也是为了避免组合继承中无可避免地要调用两次父类构造函数的最佳方案。
开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式。
本质是子类的原型继承自父类的原型,申明一个用于继承原型的 inheritPrototype 方法,通过这个方法我们能够将子类的原型指向超类的原型,从而避免超类二次实例化。
class 继承
ES6 中,通过 class 关键字来定义类,子类可以通过 extends 继承父类。
详情见
https://cloud.tencent.com/developer/article/1851143
前端开发中如何解决跨域问题_哔哩哔哩_bilibili
**同源:**协议,主机和端口都完全一样,就是同源
1、 jsonp: 利用标签没有跨域限制的漏洞,网页可以得到从其他来源动态产生的 JSON 数据。JSONP 请求一 定需要对方的服务器做支持才可以。
JSONP 优点是简单兼容性好,可用于解决主流浏览器的跨域数据访问的问题。缺点是仅支持 get 方法具有局限性,不安全可能会遭受 XSS 攻击。
声明一个回调函数,其函数名(如 show)当做参数值,要传递给跨域请求数据的服务器,函数形参为要获取目标数据(服务器返回的data)。 创建一个 `` 标签,把那个跨域的 API 数据接口地址,赋值给 script 的 src,还要在这个地址中向服 务器传递该函数名(可以通过问号传参:?callback=show)。
服务器接收到请求后,需要进行特殊的处理:把传递进来的函数名和它需要给你的数据拼接成一个字符串,例如:传递进去的函数名是 show,它准备好的数据是 show(‘我不爱你’) 。
最后服务器把准备的数据通过 HTTP 协议返回给客户端,客户端再调用执行之前声明的回调函数 (show),对返回的数据进行操作。
2、在后端解决跨域问题:cors
若后端使用Nodejs,可以使用cors中间件来解决跨域问题,cors默认允许所有跨域请求,可以通过配置origin属性,在里面添加允许跨域的域名。
CORS:跨域资源共享(CORS)是一种机制;当一个资源访问到另外一个资源(这个资源放在 不同的域名或者不同的协议或者端口),资源就会发起一个跨域的 HTTP 请求需要浏览器和服务器同时支持;
1.整个 CORS 通信,都是浏览器自动完成。浏览器发现了 AJAX 请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉;
2.实现 CORS 的关键是服务器,只要服务器实现了 CORS 接口,就可以跨源通信
3.服务器对于不同的请求,处理方式不一样; 有简单请求和非简单请求
3、在前端解决跨域问题:配置代理
因为跨域是浏览器的保护机制,那么只要脱离浏览器发送请求,就不会收到跨域保护机制的影响,所以可以使用一个中转服务器来发送请求和接收响应,前端只要接受中转服务器的响应,和中转服务器的url保持一致就可以了。
与服务器交互:
存储大小:
cookie 数据根据不同浏览器限制,大小一般不能超过 4k
sessionStorage 和 localStorage 虽然也有存储大小的限制,但比 cookie 大得多,可以达到 5M 或更大
有期时间:
1、cookie 数据存放在客户的浏览器上,session 数据放在服务器上。
2、cookie 不是很安全,别人可以分析存放在本地的 COOKIE 并进行 COOKIE 欺骗
考虑到安全应当使用 session。
3、session 会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能
考虑到减轻服务器性能方面,应当使用 COOKIE。
4、单个 cookie 保存的数据不能超过 4K,很多浏览器都限制一个站点最多保存 20 个 cookie。
2. cookie 能做什么?
用户在第一次登录某个网站时,要输入用户名密码,如果觉得很麻烦,下次登录时不想输入了,那么就在第一次登录时将登录信息存放在 cookie 中。下次登录时我们就可以直接获取 cookie 中的用户名密码来进行登录。
PS:虽然 浏览器将信息保存在 cookie 中是加密了,但是可能还是会造成不安全的信息泄露
类似于购物车性质的功能,第一次用户将某些商品放入购物车了,但是临时有事,将电脑关闭了,下次再次进入此网站,我们可以通过读取 cookie 中的信息,恢复购物车中的物品。
PS:实际操作中,这种方法很少用了,基本上都是将这些信息存储在数据库中。然后通过查询数据库的信息来恢复购物车里的物品
页面之间的传值。在实际开发中,我们往往会通过一个页面跳转到另外一个页面。后端服务器我们可以通过数据库,session 等来传递页面所需要的值。但是在浏览器端,我们可以将数据保存在 cookie 中,然后在另外页面再去获取 cookie 中的数据。
PS:这里要注意 cookie 的时效性,不然会造成获取 cookie 中数据的混乱。
js 是一个单线程、异步、非阻塞 I/O 模型、 event loop 事件循环的执行机制
所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。
同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务。异步 任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程, 某个异步任务可以执行了,该任务才会进入主线程执行。
浅拷贝: 创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以如果其中一个对象改变了这个 地址,就会影响到另一个对象。
深拷贝: 会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即 发生深拷贝。 深拷贝相比于浅拷贝速度较慢并且花销较大。拷贝前后两个对象互不影响。
实现浅拷贝方法
(1)Object.assign方法
var obj = {
a: 1,
b: 2
}
var obj1 = Object.assign({},obj);
boj1.a = 3;
console.log(obj.a) // 3
(2)for in方法
// 只复制第一层的浅拷贝
function simpleCopy(obj1) {
var obj2 = Array.isArray(obj1) ? [] : {};
for (let i in obj1) {
obj2[i] = obj1[i];
}
return obj2;
}
var obj1 = {
a: 1,
b: 2,
c: {
d: 3
}
}
var obj2 = simpleCopy(obj1);
obj2.a = 3;
obj2.c.d = 4;
alert(obj1.a); // 1
alert(obj2.a); // 3
alert(obj1.c.d); // 4
alert(obj2.c.d); // 4
实现深拷贝方法
(1)采用递归去拷贝所有层级属性
function deepClone(obj){
let objClone = Array.isArray(obj)?[]:{};
if(obj && typeof obj==="object"){
for(key in obj){
if(obj.hasOwnProperty(key)){
//判断ojb子元素是否为对象,如果是,递归复制
if(obj[key]&&typeof obj[key] ==="object"){
objClone[key] = deepClone(obj[key]);
}else{
//如果不是,简单复制
objClone[key] = obj[key];
}
}
}
}
return objClone;
}
let a=[1,2,3,4],
b=deepClone(a);
a[0]=2;
console.log(a,b);
(2)使用JSON.stringify和JSON.parse实现深拷贝:JSON.stringify把对象转成字符串,再用JSON.parse把字符串转成新的对象;
function deepCopy(obj1){
let _obj = JSON.stringify(obj1);
let obj2 = JSON.parse(_obj);
return obj2;
}
var a = [1, [1, 2], 3, 4];
var b = deepCopy(a);
b[1][0] = 2;
alert(a); // 1,1,2,3,4
alert(b); // 2,2,2,3,4
(3)热门的函数库lodash,也有提供_.cloneDeep用来做深拷贝;
var _ = require('lodash');
var obj1 = {
a: 1,
b: { f: { g: 1 } },
c: [1, 2, 3]
};
var obj2 = _.cloneDeep(obj1);
console.log(obj1.b.f === obj2.b.f);
// false
// 快速排序:取一个值作为参考值,比参考值小的放左边,比参考值大的放右边,同时不断递归,实现排序
function quickSort(elements) {
// 递归出口
if (elements.length <= 1) {
return elements;
}
// 取数组中间值为初始的索引值
var pivotIndex = Math.floor(elements.length / 2);
// 取出中间索引对应的值作为参考值
var pivot = elements.splice(pivotIndex, 1)[0];
var left = [];
var right = [];
// 比参考值小的放到左边,比参考值大的放到右边
for (var i = 0; i < elements.length; i++) {
if (elements[i] < pivot) {
left.push(elements[i]);
} else {
right.push(elements[i]);
}
}
return quickSort(left).concat([pivot],quickSort(right));
}
var elements = [3,5,6,8,2,4,7,9,1,10];
console.log(quickSort(elements))
// 插入排序
function sort(elements) {
// 假设第0个元素是一个有序数列,第1个以后的是无序数列
// 所以从第1个元素开始将无序数列的元素插入到有序数列中
for (var i = 1; i <= elements.length; i++) {
// 升序
// 遇到第一个无序的数字了
if (elements[i] < elements[i - 1]) {
// 把这个无序的数字存储起来
var guard = elements[i];
// 记住有序数列的最后一个位置,将有序数列的最后一个数往后挪一位,给前面找出来的那个无序的小数字腾个空
var j = i - 1;
elements[i] = elements[j];
// 至于 guard具体要插入到哪里,就需要一个个与前面的有序数字比较了,如果guard比这些数字小,这些数字就都后移一位
while (i >= 0 && guard < elements[j]) {
elements[j + 1] = elements[j];
j--;
}
// 找到插入的位置了
elements[j + 1] = guard;
}
}
}
// 冒泡排序
function sort(elements) {
for (var i = 0; i < ekements.length - 1;i++) {
for (var j = 0; j < elements.length - 1 -i; j++) {
if (elements[j] > elements[j + 1]) {
var swap = elements[j];
elements[j] = elements[j + 1];
elements[j + 1] = swap;
}
}
}
}
// 箭头函数
let fun = (name) => {
// 函数体
return `Hello ${name} !`;
};
// 等同于
let fun = function (name) {
// 函数体
return `Hello ${name} !`;
};
可以看出,定义箭头函在数语法上要比普通函数简洁得多。箭头函数省去了function
关键字,采用箭头=>
来定义函数。函数的参数放在=>
前面的括号中,函数体跟在=>
后的花括号中。
关于箭头函数的参数:
① 如果箭头函数没有参数,直接写一个空括号即可。
② 如果箭头函数的参数只有一个,也可以省去包裹参数的括号。
③ 如果箭头函数有多个参数,将参数依次用逗号(,)分隔,包裹在括号中即可。
// 没有参数
let fun1 = () => {
console.log(111);
};
// 只有一个参数,可以省去参数括号
let fun2 = name => {
console.log(`Hello ${name} !`)
};
// 有多个参数
let fun3 = (val1, val2, val3) => {
return [val1, val2, val3];
};
关于箭头函数的函数体:
① 如果箭头函数的函数体只有一句代码,就是简单返回某个变量或者返回一个简单的JS表达式,可以省去函数体的大括号{ }。
let f = val => val;
// 等同于
let f = function (val) { return val };
let sum = (num1, num2) => num1 + num2;
// 等同于
let sum = function(num1, num2) {
return num1 + num2;
};
② 如果箭头函数的函数体只有一句代码,就是返回一个对象,可以像下面这样写:
// 用小括号包裹要返回的对象,不报错
let getTempItem = id => ({ id: id, name: "Temp" });
// 但绝不能这样写,会报错。
// 因为对象的大括号会被解释为函数体的大括号
let getTempItem = id => { id: id, name: "Temp" };
③ 如果箭头函数的函数体只有一条语句并且不需要返回值(最常见是调用一个函数),可以给这条语句前面加一个void
关键字
let fn = () => void doesNotReturn();
1. 箭头函数没有自己的 this,this 指向定义箭头函数时所处的外部执行环境的 this,即使调用call/apply/bind也无法改变箭头函数的 this
!!! 箭头函数继承而来的this指向永远不变(重要!!深入理解!!)
箭头函数是匿名函数,
箭头函数不能作为构造函数使用,箭头函数不能 new,会报错
箭头函数没有 arguments,在箭头函数内访问这个变量访问的是外部执行环境的 arguments ,可以在箭头函数中使用rest参数代替arguments对象,来访问箭头函数的参数列表!!
5. 箭头函数没有 prototype
箭头函数不能用作Generator函数,不能使用yeild关键字
共同点 : 都可以改变 this 指向;
不同点: call 和 apply 会调用函数, 并且改变函数内部 this 指向. call 和 apply传递的参数不一样,call 传递参数使用逗号隔开,apply 使用数组传递 ,bind 不会调用函数, 可以改变函 数内部 this 指向.
应用场景
作用:
都可以改变函数内部的this指向。
区别点:
解析:
call方法
JavaScript 函数 Call (w3school.com.cn)
改变函数内部this指向
call()方法调用一个对象。简单理解为调用函数的方式,但是它可以改变函数的this指向。
写法:fun.call(thisArg, arg1, arg3, …) // thisArg为想要指向的对象,arg1,arg3为参数
call 的主要作用也可以实现继承
function Person(uname, age) {
this.uname = uname;
this.age = age;
}
function Son(uname, age) {
Person.call(this, uname, age);
}
var son = new Son("zhang", 12);
console.log(son);//Son { uname: 'zhang', age: 12 }
apply方法
JavaScript 函数 Apply (w3school.com.cn)
apply()方法调用一个函数。简单理解为调用函数的方式,但是它可以改变函数的this指向。
写法:fun.apply(thisArg, [argsArray])
apply的主要应用,比如可以利用apply可以求得数组中最大值
const arr = [1, 22, 3, 44, 5, 66, 7, 88, 9];
const max = Math.max.apply(Math, arr);
console.log(max);
bind()方法不会调用函数,但是能改变函数内部this指向
写法:fun.bind(thisArg, arg1, arg2, …)
var o = { name: "lisa" };
function fn() {
console.log( this );
}
var f = fn.bind(o);
f();
bind应用
如果有的函数我们不需要立即调用,但是又需要改变这个函数的this指向,此时用bind再合适不过了
const btns = document.querySelectorAll("button");
for(let i = 0; i < btns.length; i++){
btns[i].onclick = function() {
this.disabled = true;
setTimeout(function() {
this.disabled = false;
}.bind(this),2000);
};
}
扩展:
主要应用场景:
在 JavaScript 中, constructor 属性返回对象的构造函数。
返回值是函数的引用,不是函数名:
JavaScript 数组 constructor 属性返回 function Array() { [native code] }
JavaScript 数字 constructor 属性返回 function Number() { [native code] }
JavaScript 字符串 constructor 属性返回 function String() { [native code] }
如果一个变量是数组你可以使用 constructor 属性来定义。
function test() {
this.name = "test";
}
test.prototype = {
a:{},
b:{}
}
var c = new test();
这主要和js的用途有关,js是作为浏览器的脚本语言,主要是实现用户与浏览器的交互,以及操作dom;这决定了它只能是单线程,否则会带来很复杂的同步问题。 举个例子:如果js被设计了多线程,如果有一个线程要修改一个dom元素,另一个线程要删除这个dom元素,此时浏览器就会一脸茫然,不知所措。所以,为了避免复杂性,从一诞生,JavaScript就是单线程,这已经成了这门语言的核心特征,将来也不会改变。
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源而造成阻塞的现象,若无外力作用,它们都将无法继续执行
产生原因
产生条件
解决办法
只要打破四个必要条件之一就能有效预防死锁的发生
参考答案:
概念:
封装: 将对象运行所需的资源封装在程序对象中——基本上,是方法和数据。对象是“公布其接口”。其他附加到这些接口上的对象不需要关心对象实现的方法即可使用这个对象。这个概念就是“不要告诉我你是怎么做的,只要做就可以了。”对象可以看作是一个自我包含的原子。对象接口包括了公共的方法和初始化数据。
继承: 继承可以解决代码复用,让编程更加靠近人类思维。当多个类存在相同的属性(变量)和方法时,可以从这些类中抽象出父类,在父类中定义这些相同的属性和方法,所有的子类不需要重新定义这些属性和方法,只需要通过继承父类中的属性和方法。
多态: 多态是指一个引用(类型)在不同情况下的多种状态。也可以理解成:多态是指通过指向父类的引用,来调用在不同子类中实现的方法。
特点:
封装可以隐藏实现细节,使得代码模块化;
继承可以扩展已存在的代码模块(类),它们的目的都是为了——代码重用。
多态就是相同的事物,调用其相同的方法,参数也相同时,但表现的行为却不同。多态分为两种,一种是行为多态与对象的多态
函数防抖和函数节流:优化高频率执行js代码的一种手段,js中的一些事件如浏览器的resize、scroll,鼠标的mousemove、mouseover,input输入框的keypress等事件在触发时,会不断地调用绑定在事件上的回调函数,极大地浪费资源,降低前端性能。为了优化体验,需要对这类事件进行调用次数的限制。
防抖: 在 一个事件 发生 一定时间 之后,才执行 特定动作
在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。
节流: https://segmentfault.com/a/1190000019577510
在 一定时间 之内,限制 一个动作 只 执行一次
主要实现思路就是通过 setTimeout定时器,通过设置延时时间,在第一次调用时,创建定时器,先设定一个变量true,写入需要执行的函数。第二次执行这个函数时,会判断变量是否true,是则返回。当第一次的定时器执行完函数最后会设定变量为false。那么下次判断变量时则为false,函数会依次运行。目的在于在一定的时间内,保证多次函数的请求只执行最后一次调用。
https://www.nowcoder.com/study/live/691/2/16
var test = window.location.href;
alert(test);
// 返回:http://i.cnblogs.com/EditPosts.aspx?opt=1
var test = window.location.protocol;
alert(test);
//返回:http:
var test = window.location.host;
alert(test);
//返回:i.cnblogs.com
var test = window.location.port;
alert(test);
//返回:空字符(如果采用默认的80端口 (update:即使添加了:80),那么返回值并不是默认的80而是空字符)
var test = window.location.pathname;
alert(test);
//返回:/EditPosts.aspx
var test = window.location.search;
alert(test);
//返回:?opt=1
(PS:获得查询(参数)部分,除了给动态语言赋值以外,我们同样可以给静态页面,并使用javascript来获得相信应的参数值。)
var test = window.location.hash;
alert(test);
//返回:空字符(因为url中没有)
js获取url中的参数值
正则法
function getQueryString(name) {
var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)', 'i');
var r = window.location.search.substr(1).match(reg);
if (r != null) {
return unescape(r[2]);
}
return null;
}
// 这样调用:
alert(GetQueryString("参数名1"));
alert(GetQueryString("参数名2"));
alert(GetQueryString("参数名3"));
split拆分法
function GetRequest() {
var url = location.search; //获取url中"?"符后的字串
var theRequest = new Object();
if (url.indexOf("?") != -1) {
var str = url.substr(1);
strs = str.split("&");
for(var i = 0; i < strs.length; i ++) {
theRequest[strs[i].split("=")[0]] = unescape(strs[i].split("=")[1]);
}
}
return theRequest;
}
var Request = new Object();
Request = GetRequest();<br>// var id=Request["id"];
// var 参数1,参数2,参数3,参数N;
// 参数1 = Request['参数1'];
// 参数2 = Request['参数2'];
// 参数3 = Request['参数3'];
// 参数N = Request['参数N'];
指定取
比如说一个url:http://i.cnblogs.com/?j=js, 我们想得到参数j的值,可以通过以下函数调用。
function GetQueryString(name) {
var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i");
var r = window.location.search.substr(1).match(reg); //获取url中"?"符后的字符串并正则匹配
var context = "";
if (r != null)
context = r[2];
reg = null;
r = null;
return context == null || context == "" || context == "undefined" ? "" : context;
}
alert(GetQueryString("j"));
单个参数的获取方法
function GetRequest() {
var url = location.search; //获取url中"?"符后的字串
if (url.indexOf("?") != -1) {? //判断是否有参数
var str = url.substr(1); //从第一个字符开始 因为第0个是?号 获取所有除问号的所有符串
strs = str.split("=");? //用等号进行分隔 (因为知道只有一个参数
//所以直接用等号进分隔 如果有多个参数 要用&号分隔 再用等号进行分隔)
alert(strs[1]);???? //直接弹出第一个参数 (如果有多个参数 还要进行循环的)
}
}
使用 ES5 语法来实现虽然会麻烦些,但兼容性最好,不用考虑浏览器 JavaScript 版本。也不用引入其他第三方库。
直接使用 filter、concat 来计算
var a = [1,2,3,4,5]
var b = [2,4,6,8,10]
//交集
var c = a.filter(function(v){ return b.indexOf(v) > -1 })
//差集
var d = a.filter(function(v){ return b.indexOf(v) == -1 })
//补集
var e = a.filter(function(v){ return !(b.indexOf(v) > -1) })
.concat(b.filter(function(v){ return !(a.indexOf(v) > -1)}))
//并集
var f = a.concat(b.filter(function(v){ return !(a.indexOf(v) > -1)}));
ES6 中可以借助扩展运算符(…)以及 Set 的特性实现相关计算,代码也会更加简单些。
var a = [1,2,3,4,5]
var b = [2,4,6,8,10]
console.log("数组a:", a);
console.log("数组b:", b);
var sa = new Set(a);
var sb = new Set(b);
// 交集
let intersect = a.filter(x => sb.has(x));
// 差集
let minus = a.filter(x => !sb.has(x));
// 补集
let complement = [...a.filter(x => !sb.has(x)), ...b.filter(x => !sa.has(x))];
// 并集
let unionSet = Array.from(new Set([...a, ...b]));
function isEmpty(value) {
return (
value === null || value === undefined ||
(typeof value === 'object' && Object.keys(value).length === 0)
)
}
方法一:
function strReverse(str) {
return str.split("").reverse().join("")
}
方法二:
function strReverse(str) {
var i=str.length;
var nstr = "";
i=i-1;
for (var x = i; x >=0; x--) {
nstr+=str.charAt(x)
}
return nstr
}
方法三:
function strReverse(str) {
if(str.length == 0)return null;
var i = str.length;
var dstr = "";
while(--i >= 0)
{
dstr += str.charAt(i);
}
return dstr;
}
方法四:
function strReverse(str) {
//先将字符串分割成一个个字符,然后使用reduce将字符反向拼接
return str.split('').reduce((prev, next) => next + prev);
}
方法五:
function strReverse(str) {
var newstr="";
for(var i=0;i<str.length;i++){
newstr=str.charAt(i) + newstr;
}
return newstr
}
方法六:
function strReverse(str) {
if(str.length===1){
return str
}
//每次将最后的单词拼到头部,并且递归字符串(每次删除最后一个字符)
return str.slice(-1)+strReverse(str.slice(0,-1));
}
对字符串进行遍历,使用String.prototype.indexOf()
实时获取遍历过程中的无重复子串并存放于str
,并保存当前状态最长无重复子串的长度为res
,当遍历结束时,res的值即为无重复字符的最长子串的长度。
/**
* @param {string} s
* @return {number}
*/
var lengthOfLongestSubstring = function(s) {
var res = 0; // 用于存放当前最长无重复子串的长度
var str = ""; // 用于存放无重复子串
var len = s.length;
for(var i = 0; i < len; i++) {
var char = s.charAt(i);
var index = str.indexOf(char);
if(index === -1) {
//若是无重复的字符就加入字符串
str += char;
res = res < str.length ? str.length : res;
} else {
//遇到了重复的字符,只保留第一个重复字符的后一个字符开始的字符串内容加当前字符串
str = str.substr(index + 1) + char;
}
}
return res;
};
console.log([]==[]); // false
console.log([]== 0); // true
原始值的比较是值的比较:
它们的值相等时它们就相等(==)
对象和原始值不同,对象的比较并非值的比较,而是引用的比较:
即使两个对象包含同样的属性及相同的值,它们也是不相等的
即使两个数组各个索引元素完全相等,它们也是不相等的,所以[]!=[]
[]==0,是数组进行了隐式转换,空数组会转换成数字0,所以相等
思路:排序,去重,双指针。
给定一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?找出所有满足条件且不重复的三元组。
注意:答案中不可以包含重复的三元组。
//例如, 给定数组 nums = [-1, 0, 1, 2, -1, -4],
//满足要求的三元组集合为:
[
[-1, 0, 1],
[-1, -1, 2]
]
解答
这题我们才用排序+双指针的思路来做,遍历排序后的数组,定义指针l和r,分别从当前遍历元素的下一个元素和数组的最后一个元素往中间靠拢,计算结果跟目标对比。
var threeSum = function(nums) {
if(nums.length < 3){
return [];
}
let res = [];
// 排序
nums.sort((a, b) => a - b);
for(let i = 0; i < nums.length; i++){
if(i > 0 && nums[i] == nums[i-1]){
// 去重
continue;
}
if(nums[i] > 0){
// 若当前元素大于0,则三元素相加之后必定大于0
break;
}
// l为左下标,r为右下标
let l = i + 1; r = nums.length - 1;
while(l<r){
let sum = nums[i] + nums[l] + nums[r];
if(sum == 0){
res.push([nums[i], nums[l], nums[r]]);
while(l < r && nums[l] == nums[l+1]){
l++
}
while(l < r && nums[r] == nums[r-1]){
r--;
}
l++;
r--;
}
else if(sum < 0){
l++;
}
else if(sum > 0){
r--;
}
}
}
return res;
};
HTML 被 HTML 解析器解析成 DOM 树;
CSS 被 CSS 解析器解析成 CSSOM 树;
结合 DOM 树和 CSSOM 树,生成一棵渲染树(Render Tree),这一过程称为 Attachment;
生成布局(flow),浏览器在屏幕上“画”出渲染树中的所有节点;
将布局绘制(paint)在屏幕上,显示出整个页面。
不同的浏览器内核不同,所以渲染过程不太一样。
重排(Reflow):当渲染树的一部分必须更新并且节点的尺寸发生了变化,浏览器会使渲染树中受到影响的部分失效,并重新构造渲染树。
重绘(Repaint):是在一个元素的外观被改变所触发的浏览器行为,浏览器会根据元素的新属性重新绘制,使元素呈现新的外观。比如改变某个元素的背景色、文字颜色、边框颜色等等
区别:重绘不一定需要重排(比如颜色的改变),重排必然导致重绘(比如改变网页位置)
引发重排
4.1 添加、删除可见的dom
4.2 元素的位置改变
4.3 元素的尺寸改变(外边距、内边距、边框厚度、宽高等几何属性)
4.4 页面渲染初始化
4.5 浏览器窗口尺寸改变
4.6 获取某些属性。当获取一些属性时,浏览器为取得正确的值也会触发重排,它会导致队列刷新,这些属性包括:offsetTop、offsetLeft、 offsetWidth、offsetHeight、scrollTop、scrollLeft、scrollWidth、scrollHeight、clientTop、clientLeft、clientWidth、clientHeight、getComputedStyle() (currentStyle in IE)。所以,在多次使用这些值时应进行缓存。
优化:
浏览器自己的优化:
浏览器会维护1个队列,把所有会引起重排,重绘的操作放入这个队列,等队列中的操作到一定数量或者到了一定时间间隔,浏览器就会flush队列,进行一批处理,这样多次重排,重绘变成一次重排重绘
减少 reflow/repaint:
(1)不要一条一条地修改 DOM 的样式。可以先定义好 css 的 class,然后修改 DOM 的 className。
(2)不要把 DOM 结点的属性值放在一个循环里当成循环里的变量。
(3)为动画的 HTML 元件使用 fixed 或 absoult 的 position,那么修改他们的 CSS 是不会 reflow 的。
(4)千万不要使用 table 布局。因为可能很小的一个小改动会造成整个 table 的重新布局。(table及其内部元素除外,它可能需要多次计算才能确定好其在渲染树中节点的属性,通常要花3倍于同等元素的时间。这也是为什么我们要避免使用table做布局的一个原因。)
(5)不要在布局信息改变的时候做查询(会导致渲染队列强制刷新)
https://www.bilibili.com/video/BV1kf4y1U7Ln/spm_id_from=333.788&vd_source=4ca3ad38fe3439acce886a777d9a94c4
主线程从"任务队列"中读取执行事件,这个过程是循环不断的,这个机制被称为事件循环。此机制具体如下:主 线程会不断从任务队列中按顺序取任务执行,每执行完一个任务都会检查微任务队列是否为空(执行完一个 任务的具体标志是函数执行栈为空),如果不为空则会一次性执行完所有微任务。然后再进入下一个循环去 任务队列中取下一个任务执行。
详细步骤:
选择当前要执行的宏任务队列,选择一个最先进入任务队列的宏任务,如果没有宏任务可以选择,则会 跳转至微任务的执行步骤。
将事件循环的当前运行宏任务设置为已选择的宏任务。
运行宏任务。
将事件循环的当前运行任务设置为null。
将运行完的宏任务从宏任务队列中移除。
6.微任务步骤:进入为热内检查点。
更新界面渲染。
返回第一步。
执行进入microtask检查的的具体步骤如下:
设置进入microtask检查点的标志为true。
当事件循环的微任务队列不为空时:选择一个最先进入microtask队列的microtask;设置事件循环的当 前运行任务为已选择的microtask;运行microtask;设置事件循环的当前运行任务为null;将运行结束 的microtask从microtask队列中移除。
对于相应事件循环的每个环境设置对象(environment settings object),通知它们哪些promise为 rejected。
清理indexedDB的事务。
设置进入microtask检查点的标志为false。
需要注意的是:当前执行栈执行完毕时会立刻先处理所有微任务队列中的事件, 然后再去宏任务队列中取出一个 事件。同一次事件循环中, 微任务永远在宏任务之前执行。
浏览器的 Javascript 具有自动垃圾回收机制(GC:Garbage Collecation),也就是说,执行环境会负责管理代码执行过程中使用的内存。其原理是:垃圾收集器会定期(周期性)找出那些不在继续使用的变量,然后释放其内存。但是这个过程不是实时的,因为其开销比较大并且GC时停止响应其他操作,所以垃圾回收器会按照固定的时间间隔周期性的执行。
不再使用的变量也就是生命周期结束的变量,当然只可能是局部变量,全局变量的生命周期直至浏览器卸载页面才会结束。局部变量只在函数的执行过程中存在,而在这个过程中会为局部变量在栈或堆上分配相应的空间,以存储它们的值,然后在函数中使用这些变量,直至函数结束,而闭包中由于内部函数的原因,外部函数并不能算是结束。
什么时候触发垃圾回收?
垃圾回收器周期性运行,如果分配的内存非常多,那么回收工作也会很艰巨,确定垃圾回收时间间隔就变成了一个值得思考的问题。IE6的垃圾回收是根据内存分配量运行的,当环境中存在256个变量、4096个对象、64k的字符串任意一种情况的时候就会触发垃圾回收器工作,看起来很科学,不用按一段时间就调用一次,有时候会没必要,这样按需调用不是很好吗?但是如果环境中就是有这么多变量等一直存在,现在脚本如此复杂,很正常,那么结果就是垃圾回收器一直在工作,这样浏览器就没法儿玩儿了。
微软在IE7中做了调整,触发条件不再是固定的,而是动态修改的,初始值和IE6相同,如果垃圾回收器回收的内存分配量低于程序占用内存的15%,说明大部分内存不可被回收,设的垃圾回收触发条件过于敏感,这时候把临街条件翻倍,如果回收的内存高于85%,说明大部分内存早就该清理了,这时候把触发条件置回。这样就使垃圾回收工作职能了很多
优缺点
使用情况
顺序表与链表的比较
能
https://juejin.cn/post/7016868803069886471
ajax:异步的javascript和XML
ajax 是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术
传统的网页(即不用ajax技术的网页),想要更新内容或者提交一个表单,都需要重新加载整个网页。使用ajax技术的网页,通过在后台服务器进行少量的数据交换,就可以实现异步局部更新。
在项目中使用npm run build进行打包
webpack 是一种前端资源构建工具,一个静态模块打包器(module bundler)。
在 webpack 看来, 前端的所有资源文件(js/json/css/img/less/…)都会作为模块处理。
它将根据模块的依赖关系进行静态分析,打包生成对应的静态资源(bundle)
webPack是一个前端模块化方案,侧重模块打包,把开发中的所有资源(图片、js文件、css文件等)都看成模块,通过loader(加载器)和plugins(插件)对资源进行处理,打包成符合生产环境部署的前端资源
1.2 webpack 五个核心概念
1.2.1 Entry
入口(Entry)指示 webpack 以哪个文件为入口起点开始打包,分析构建内部依赖图。
1.2.2 Output
输出(Output)指示 webpack 打包后的资源 bundles 输出到哪里去,以及如何命名。
1.2.3 Loader
Loader 让 webpack 能 够 去 处 理 那 些 非 JavaScript 文 件 (webpack 自 身 只 理 解
JavaScript)
1.2.4 Plugins
插件(Plugins)可以用于执行范围更广的任务。插件的范围包括,从打包优化和压缩,
一直到重新定义环境中的变量等。
1.2.5 Mode
模式(Mode)指示 webpack 使用相应模式的配置。(development / production)
module.exports = {
entry: './src/js/index.js', // 入口文件
output: { // 输出配置
filename: './built.js', // 输出文件名
path: resolve(__dirname, 'build/js') // 输出文件路径配置
},
mode: 'development' //开发环境
};
loader即为文件加载器,操作的是文件,将文件A通过loader转换成文件B,是一个单纯的文件转化过程。
plugin即为插件,是一个扩展器,丰富webpack本身,增强功能 ,针对的是在loader结束之后,webpack打包的整个过程,他并不直接操作文件,而是基于事件机制工作,监听webpack打包过程中的某些节点,执行广泛的任务。
Git 是一个开源的分布式版本控制系统,用于敏捷高效地处理任何或小或大的项目。
https://www.cnblogs.com/tocy/p/git-stash-reference.html添加链接描述
综合下网上的介绍和资料,git stash(git储藏)可用于以下情形:
发现有一个类是多余的,想删掉它又担心以后需要查看它的代码,想保存它但又不想增加一个脏的提交。这时就可以考虑git stash
。
使用git的时候,我们往往使用分支(branch)解决任务切换问题,例如,我们往往会建一个自己的分支去修改和调试代码, 如果别人或者自己发现原有的分支上有个不得不修改的bug,我们往往会把完成一半的代码commit提交到本地仓库,然后切换分支去修改bug,改好之后再切换回来。这样的话往往log上会有大量不必要的记录。其实如果我们不想提交完成一半或者不完善的代码,但是却不得不去修改一个紧急Bug,那么使用git stash
就可以将你当前未提交到本地(和服务器)的代码推入到Git的栈中,这时候你的工作区间和上一次提交的内容是完全一样的,所以你可以放心的修Bug,等到修完Bug,提交到服务器上后,再使用git stash apply
将以前一半的工作应用回来。
经常有这样的事情发生,当你正在进行项目中某一部分的工作,里面的东西处于一个比较杂乱的状态,而你想转到其他分支上进行一些工作。问题是,你不想提交进行了一半的工作,否则以后你无法回到这个工作点。解决这个问题的办法就是git stash命令。储藏(stash)可以获取你工作目录的中间状态——也就是你修改过的被追踪的文件和暂存的变更——并将它保存到一个未完结变更的堆栈中,随时可以重新应用。
https://blog.csdn.net/terrychinaz/article/details/112426029
ES6 中提供的 class 和 extends 本质上只是语法糖,底层的实现原理依然是构造函数和寄生组合式继承。 所以对于一个合格的前端工程师来说,即使 ES6 已经到来,对于 ES5 中的这些基础原理我们依然需要好好掌握。
https://blog.csdn.net/LttleJoker/article/details/109282792
JavaScript中异步编程的方法有:
promise是es6推出的,可以解决以前写异步代码使用回调函数的回调地狱的问题,他有三种状态,pending
(进行中)、resolved
(已完成)、rejected
(已失败),只有异步操作的结果可以决定当前是哪一种状态,任何其他操作都不能改变这个状态, Promise
构造函数的原型对象上,有then
()和catch
()等方法,then
()第一个参数接收resolved()
传来的数据,catch()
第一个参数接收rejected()
传来的数据,但是promise会形成链式调用,不够美观,
async/await是ES7新特性,async/await是基于Promise实现的,await 操作符用于等待一个 Promise 对象。它只能在异步函数 async function 中使用,async 函数的返回值为 Promise 对象,async 函数返回的 Promise 的结果由函数执行的结果决定,await 必须写在 async 函数中, 但 async 函数中可以没有 await,如果 await 的 Promise 失败了, 就会抛出异常, 需要通过 try…catch 捕获处理。
function fn2() {
return new Promise((resolve, reject) => {
setTimeout(() => {
// resolve(1000)
reject(1000)
}, 1000);
})
}
async function fn3() {
try {
const value = await fn2()
} catch (error) {
console.log('得到失败的结果', error)
}
}
fn3() // 得到失败的结果 1000
之前只知道typeof null = object,但是却从来不知道是为什么。最新查阅资料的时候,看到了这个原理,记录下来,方便自己以后查看。
原理是这样的,不同的对象在底层都表示为二进制,在 JavaScript 中二进制前三位都为 0 的话会被判
断为 object 类型, null 的二进制表示是全 0,自然前三位也是 0,所以执行 typeof 时会返回“ object ”。
这个bug是第一版Javascript留下来的。在这个版本,数值是以32字节存储的,由标志位(1~3个字节)和数值组成。标志位存储的是低位的数据。这里有五种标志位:
000:对象,数据是对象的应用。
1:整型,数据是31位带符号整数。
010:双精度类型,数据是双精度数字。
100:字符串,数据是字符串。
110:布尔类型,数据是布尔值。
最低位有一位,那么标志位只有一个1字节长度;或者是零位,标志位有3个字节长度,多出两个了字节,一共多出四种类型。
https://blog.csdn.net/chenjuan1993/article/details/81347590
https://juejin.cn/post/7070363924083769358
冒泡事件:比如说鼠标点击了一个按钮,同样的事件将会在那个元素的所有祖先元素中被触发。这一过程被称为事件冒泡。
<div class="box">
< button class="btn">按钮</button>
</div>
<script type="text/javascript">
$('.btn').click(function () {
alert('按钮被点击了')
});
$('.box').click(function () {
alert('box被点击了')
})
</script>
当我们点击按钮后,因为按钮也属于.box元素,所以按钮的父元素.box也会触发点击事件
阻止冒泡事件有三种方法:
1.event.stopPropagation()方法
$('.btn').click(function (even) {
even.stopPropagation();
alert('按钮被点击了');
})
这是阻止事件的冒泡方法,不让事件向documen上蔓延,但是默认事件任然会执行,当你调用这个方法的时候,如果点击一个连接,这个连接仍然会被打开。
例如:
<a href="https://www.csdn.net/" class="box">
<button class="btn">按钮</button>
</a>
2.event.preventDefault()方法
$('.btn').click(function (even) {
even.preventDefault();
alert('按钮被点击了');
})
这是阻止默认事件的方法,调用此方法是,连接不会被打开,但是会发生冒泡,冒泡会传递到上一层的父元素;
3.return false ;
$('.btn').click(function (even) {
alert('按钮被点击了');
return false;
})
这个方法比较暴力,他会同事阻止事件冒泡也会阻止默认事件;写上此代码,连接不会被打开,事件也不会传递到上一层的父元素;可以理解为return false就等于同时调用了event.stopPropagation()和event.preventDefault()
//构造函数的写法
var Person = function (name, age) {
//这里定义的是实例属性
this.name = name;
this.age = age;
//这里定义的是实例方法
this.sayHello = function () {
console.log("我是实例方法" + this.name);
};
};
};
//创建实例对象
let p1 = new Person("小米", 18);
console.log(p1);
// 添加原型链属性/方法(所有实例共享)
Person.prototype.Way2 = function () {
console.log(`我的名字是${this.name},今年${this.age}岁`);
};
//访问原型链方法
p1.Way2();
// 静态方法/静态属性(只能由构造函数本身访问)
Person.title = "这是构造函数静态属性";
console.log(Person.title);
console.log(p1.title); //undefined
//动态创建实例方法(与原型链方法重名)
p1.Way2 = function () {
console.log("我是p1的sayHello()");
};
//优先从实例方法中读取,不会去读取原型链方法
p1.Way2();//我是p1的sayHello()
ES6的class可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。
类就是将原先写在构造函数内部的实例属性和方法代码,写入到constructor()中
将原先写在原型链prototype上面的属性直接写在类里面
将静态属性直接写在类里面并且在前面添加static关键字
类的数据类型就是函数,类本身就指向构造函数
//类的写法
class Superman {
//constructor函数内部定义的是实例属性和方法
constructor(name, age) {
//这里定义的是实例属性
this.name = name;
this.age = age;
//这里定义的是实例方法
this.sayHello = function () {
console.log("我是实例方法" + this.name);
};
}
// 定义的是原型链(prototype)方法
sayHello() {
console.log(`我的名字是${this.name},今年${this.age}岁`);
}
// 静态方法(只能由类本身访问);调用方法:Superman.foo()
static foo() {
console.log("我是类的静态方法");
}
// 静态属性(只能由类本身访问);调用方法:Superman.title
static title = "这是类的标题";
}
//创建实例
var s1 = new Superman("张三", 20);
console.log(s1);
console.log(Superman);
console.dir(Superman);
// 类的本质是函数
console.log(typeof Superman); //function
console.log(s1.sayHello === Superman.prototype.sayHello); //true
登录:当用户填写完账号和密码后向服务端验证是否正确,验证通过之后,服务端会返回一个token,
拿到token之后(我会将这个token存贮到cookie中,保证刷新页面后能记住用户登录状态),前端会
根据token再去拉取一个 user_info 的接口来获取用户的详细信息(如用户权限,用户名等等信息)。
权限验证:通过token获取用户对应的 权限,动态根据用户的 权限算出其对应有权限的路由,通过
router.addRoutes 动态挂载这些路由。
具体思路:
黑马程序员登录成功后,服务端会返回一个 token(该token的是一个能唯一标示用户身份的一个key),之后我
们将token存储在本地cookie之中,这样下次打开页面或者刷新页面的时候能记住用户的登录状态,不
用再去登录页面重新登录了。
ps:为了保证安全性,我司现在后台所有token有效期(Expires/Max-Age)都是Session,就是当浏览器关
闭了就丢失了。重新打开游览器都需要重新登录验证,后端也会在每周固定一个时间点重新刷新
token,让后台用户全部重新登录一次,确保后台用户不会因为电脑遗失或者其它原因被人随意使用账
号。
用户登录成功之后,我们会在全局钩子 router.beforeEach 中拦截路由,判断是否已获得token,在
获得token之后我们就要去获取用户的基本信息了
页面会先从 cookie 中查看是否存有 token,没有,就走一遍上一部分的流程重新登录,如果有token,
就会把这个 token 返给后端去拉取user_info,保证用户信息是最新的。 当然如果是做了单点登录得功
能的话,用户信息存储在本地也是可以的。当你一台电脑登录时,另一台会被提下线,所以总会重新登
录获取最新的内容。
先说一说我权限控制的主体思路,前端会有一份路由表,它表示了每一个路由可访问的权限。当用户登
录之后,通过 token 获取用户的 role ,动态根据用户的 role 算出其对应有权限的路由,再通过
router.addRoutes 动态挂载路由。但这些控制都只是页面级的,说白了前端再怎么做权限控制都不是
绝对安全的,后端的权限验证是逃不掉的。
我司现在就是前端来控制页面级的权限,不同权限的用户显示不同的侧边栏和限制其所能进入的页面(也
做了少许按钮级别的权限控制),后端则会验证每一个涉及请求的操作,验证其是否有该操作的权限,每
一个后台的请求不管是 get 还是 post 都会让前端在请求 header 里面携带用户的 token,后端会根据
该 token 来验证用户是否有权限执行该操作。若没有权限则抛出一个对应的状态码,前端检测到该状
态码,做出相对应的操作。
使用vuex管理路由表,根据vuex中可访问的路由渲染侧边栏组件。
具体实现:
创建vue实例的时候将vue-router挂载,但这个时候vue-router挂载一些登录或者不用权限的公用的页
面。
当用户登录后,获取用role,将role和路由表每个页面的需要的权限作比较,生成最终用户可访问的路
由表。
调用router.addRoutes(store.getters.addRouters)添加用户可访问的路由。
使用vuex管理路由表,根据vuex中可访问的路由渲染侧边栏组件。
Lodash是一个一致性、模块化、高性能的 JavaScript 实用工具库。
_.cloneDeep 深度拷贝
_.reject 根据条件去除某个元素。
_.drop(array, [n=1] ) 作用:将 array 中的前 n 个元素去掉,然后返回剩余的部分.
不管怎样简单的需求,在量级达到一定层次时,都会变得异常复杂
文件上传简单,文件变大就复杂
上传大文件时,以下几个变量会影响我们的用户体验
上传时间会变长,高频次文件上传失败,失败后又需要重新上传等等
为了解决上述问题,我们需要对大文件上传单独处理
这里涉及到分片上传及断点续传两个概念
分片上传,就是将所要上传的文件,按照一定的大小,将整个文件分隔成多个数据块(Part)来进行分片上传
如下图
上传完之后再由服务端对所有上传的文件进行汇总整合成原始的文件
大致流程如下:
断点续传指的是在下载或上传时,将下载或上传任务人为的划分为几个部分
每一个部分采用一个线程进行上传或下载,如果碰到网络故障,可以从已经上传或下载的部分开始继续上传下载未完成的部分,而没有必要从头开始上传下载。用户可以节省时间,提高速度
一般实现方式有两种:
上传过程中将文件在服务器写为临时文件,等全部写完了(文件上传完),将此临时文件重命名为正式文件即可
如果中途上传中断过,下次上传的时候根据当前临时文件大小,作为在客户端读取文件的偏移量,从此位置继续读取文件数据块,上传到服务器从此偏移量继续写入文件即可
整体思路比较简单,拿到文件,保存文件唯一性标识,切割文件,分段上传,每次上传一段,根据唯一性标识判断文件上传进度,直到文件的全部片段上传完毕
详情见:https://www.jianshu.com/p/e86d92aeae69
另外,不同浏览器无法共享localStorage和sessionStorage中的信息。同一浏览器的相同域名和端口的不同页面间可以共享相同的 localStorage,但是不同页面间无法共享sessionStorage的信息。这里需要注意的是,页面仅指顶级窗口,如果一个页面包含多个iframe且他们属于同源页面,那么他们之间是可以共享sessionStorage的。在实际开发过程中,遇到的最多的问题就是localStorage的同源策略问题。为了了解这个问题,我们先得清楚什么是同源策略。同源策略(same-origin policy)是浏览器执行的一种安全措施,目的是为了保证用户信息的安全,防止恶意的网站窃取数据。
只要不同源就不能共享localStorage的数据。但是在实际开发中又时常会遇到这样的需求,那我们该如何解决呢?
目前广泛采用的是postMessage
和iframe
相结合的方法。postMessage(data,origin)方法允许来自不同源的脚本采用异步方式进行通信,可以实现跨文本档、多窗口、跨域消息传递。接受两个参数:
localStorage很多时候用来存储数据非常好,方便在页面中使用某些数据的时候调用
首先,通过setItem(key,value)即可存储数据
然后用getItem(key)的方式即可取得数据
可以看到,设置之后在浏览器的localStorage中看到存储的数据
使用localStoage存储的数据除非用户手动清空浏览器的信息,否则不会被删除
当然我们也可以通过removeItem(key)的形式来移除
removeItem会删除对应key的内容
如果想要删除全部的数据可以用clear方法来清除
1.使用 for in 循环遍历对象的属性时,原型链上的所有属性都将被访问:
Object.prototype.say="cgl"; // 修改Object.prototype
var person ={ age: 18 };
for (var key in person) {
console.log(key, person[key]);//这里用person.key得不到对象key的值,用person[key] 或者 eval("person."+key);
} //结果: age 18
say cgl
2.只遍历对象自身的属性,而不遍历继承于原型链上的属性,使用hasOwnProperty 方法过滤一下。
Object.prototype.say="cgl";
var person ={
age: 18
};
for (var key in person) {
if(person.hasOwnProperty(key)){
console.log(key, eval("person."+key));
}
}
二.Object.keys()
Object.keys() 方法会返回一个由给定对象的自身可枚举属性组成的数组,数组中属性名的排列顺序和使用 for…in 循环遍历该对象时返回的顺序一致 (两者的主要区别是 一个 for-in 循环还会枚举其原型链上的属性)。返回值是这个对象的所有可枚举属性组成的字符串数组。
Object.prototype.say="cgl";
var person ={ age: 18};
console.log(Object.keys(person));//结果 ["age"]
小技巧:object对象没有length属性,可以通过Object.keys(person).length,来获取person的长度了。
不是
setTimeou中的方法是异步任务,会被放到任务队列中,需要等执行栈中的同步任务执行完之后,任务队列中的任务才能执行,所以需要等到同步任务执行完才开始计时,因此一定会大于等于3s。
为什么要分微任务和宏任务?
微任务是线程之间的切换,速度快。不用进行上下文切换,可以快速的一次性做完所有的微任务。
宏任务是进程之间的切换,速度慢,且每次执行需要切换上下文。因此一个Eventloop中只执行一个宏任务。
而区分微任务和宏任务的根本原因是为了插队。由于微任务执行快,一次性可以执行很多个,在当前宏任务执行后立刻清空微任务可以达到伪同步的效果,这对视图渲染效果起到至关重要的作用。
反观如果不区分微任务和宏任务,那么新注册的任务不得不等到下一个宏任务结束后,才能执行。
首先: let arr = [];
从构造函数入手:可以判断一个对象是否是在其原型链上原型构造函数中的属性。
console.log(arr instanceof Array); //true
复制代码
再说说 typeof 和 instanceof 的区别? 两者都可以用来判断变量,typeof会返回基本类型,而instanceof只会返回一个布尔值。
这个属性是返回对象相对应的构造函数,Object的每个实例都有构造函数 constructor,用于保存着用于创建当前对象的函数。
console.log(arr.constructor === Array); //true
复制代码
ES5中新增了Array.isArray方法,IE8及以下不支持,用于确定传递的值是否是一个 Array。
console.log(Array.isArray(arr)); //true
复制代码
从原型入手,Array.prototype 属性表示 Array 构造函数的原型,
其中有一个方法是 isPrototypeOf() 用于测试一个对象是否存在于另一个对象的原型链上。
console.log(Array.prototype.isPrototypeOf(arr)); //true
Object.getPrototypeOf() 方法返回指定对象的原型,所以只要跟Array的原型比较即可。
console.log(Object.getPrototypeOf(arr) === Array.prototype); // true
复制代码
虽然Array也继承自Object,但js在Array.prototype上重写了toString,而我们通过toString.call(arr)实际上是通过原型链调用了。可以获取到变量的不同类型。
console.log(Object.prototype.toString.call(arr) === '[object Array]'); //true
console.log(Object.prototype.toString.call(arr).slice(8,-1)=== 'Array'); //true
console.log(Object.prototype.toString.call(arr).indexOf('Array') !== -1); //true
作者:未i
链接:https://juejin.cn/post/6976089271841226783
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
1.什么是预加载
资源预加载是另一个性能优化技术,我们可以使用该技术来预先告知浏览器某些资源可能在将来会被使用到。预加载简单来说就是将所有所需的资源提前请求加载到本地,这样后面在需要用到时就直接从缓存取资源。
2.为什么要用预加载
在网页全部加载之前,对一些主要内容进行加载,以提供给用户更好的体验,减少等待的时间。否则,如果一个页面的内容过于庞大,没有使用预加载技术的页面就会长时间的展现为一片空白,直到所有内容加载完毕。
3.实现预加载的几种办法
https://blog.csdn.net/u011215669/article/details/80526530