块标签:div p h1-h6 hr ul ol li dl dd dt form
①支持宽高,自上而下排列
②不受空格影响
③一般用于其他标签的容器
④默认宽度为100%(独占一行)。
行内标签:span i a b strong em sub sup u label br font
①不支持宽高(宽高根据内容大小自动撑开),自左向右排列
②受空格影响
③不独占一行
行内块标签:img textarea input
①支持宽高,自左向右排列
②受空格影响
③不独占一行
标签之间的转换
display:inline(转为行内元素)
inline-block(转为行内块元素)
block(转为块元素)
none(隐藏 不显示)
注意:当元素浮动(float)时会转化成行内块元素特点。
Undefined、Null、Boolean、Number 、String
Object 、 Array 、 Boolean 、 Number 和 String
js基本数据类型包括:undefined,null,number,boolean,string.
基本数据类型是按值访问的,就是说我们可以操作保存在变量中的实际的值
var s = "hello";
s.toUpperCase()//HELLO;
console.log(s)//hello
基本数据类型不可以添加属性和方法
基本数据类型的赋值是简单赋值([]=={}true)
基本数据类型的比较是值的比较
基本数据类型是存放在栈区的
js引用类型:Object,Array,Function,Data等
引用类型的值是可以改变的
引用类型可以添加属性和方法
引用类型的赋值是对象引用
引用类型的比较是引用的比较({}=={}false)
引用类型是同时保存在栈区和堆区中的
ECMAScript还提供了三个特殊的引用类型Boolean,String,Number.我们称这三个特殊的引用类型为基本包装类型,也叫包装对象
Symbol(定义一个独一无二的值)
symbol使用
let s = Symbol()
let s1 = Symbol(‘foo’);
JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。
它是基于 JavaScript 的一个子集。数据格式简单, 易于读写, 占用带宽小
JSON 字符串转换为 JSON 对象:
var obj =eval('('+ str +')');
var obj = str.parseJSON();
var obj = JSON.parse(str);
JSON 对象转换为 JSON 字符串:
var last=obj.toJSONString();
var last=JSON.stringify(obj);
obj = {a: 1,b: 2}
obj2 = {a: 1,b: 2}
obj3 = {a: 1,b: '2'}
JSON.stringify(obj)==JSON.stringify(obj2);//true
JSON.stringify(obj)==JSON.stringify(obj3);//false
undefined,null, NaN,“”,0, false——>false
null == undefined ------> true
null是一个表示空对象指针,转为数值时为0,使用typeof运算得到 “object” (程序级的、正常的或在意料之中的值的空缺)
undefined是一个表示"无"的原始值,当一个声明了一个变量未初始化时,得到的就是 undefined。转为数值时为NaN。(系统级的、出乎意料的或类似错误的值的空缺)
null == undefined // true
采坑点:
undefined + 6 // NaN
null + 6 // 6
目前的用法
null表示 “没有对象”,即该处不应该有值。
(1)作为函数的参数,表示该函数的参数不是对象。
(2)作为对象原型链的终点。
undefined表示“缺少值”,就是此处应该有一个值,但是还没有定义。
(1)变量被声明了,但没有赋值时,就等于undefined。
(2)调用函数时,应该提供的参数没有提供,该参数就等于undefined。
(3)对象没有赋值的属性,该属性的值为undefined。
(4)函数没有返回值时,默认返回undefined。
判断数据类型
typeof(number,NaN)-----number
typeof(boolean)-----boolean
typeof(string)------string
typeof(arr,object,null)----object
typeof(function)-----------function
typeof(undefined)---------undefined
用来判断对象是不是某个构造函数的实例。沿着原型链找。
undefined:[object Undefined]
null:[object Null]
Number :[object Number]
String:[object String]
true:[object Boolean]
[]:[object Array]
{}:[object Object]
function(){} :[object Function]
null
, 对象
, 函数的prototype属性
(创建空的对象时需传null , 否则会抛出TypeError
异常)。Object.defineProperties()
的第二个参数。1.Array.isArray(arr)
2.Object.prototype.toString.call(arr) === ‘[object Array]’
3.arr instanceof Array
4.array.constructor === Array
parseInt(3, 8) -------------------->以八进制为基底将3转为十进制 3
parseInt(3, 2) --------------------> 以二进制为基底将3转为十进制(3不是二进制,报错NaN)
parseInt(3, 0) -------------------->NaN / 报错
任意进制转十进制
function trans(num,base){
if(isNaN(num))return;
if(isNaN(base))return;
var arr=String(num).split('');
var res=0;
for(var i=0;i=base)return
res+=Math.pow(base,i)*arr[i];
}
return res;
}
十进制转任意进制
parseInt(num).toString(base)
str = arr.join(’ ');
slice(start,end) 返回浅拷贝数组 不改变原数组
splice(index,num) 如果删除一个元素 返回只包含该元素的数组 原数组改变
concat() 返回拼接后的数组 不改变原数组
"use strict"
var a = 123;
eval('console.log(a)'); //将字符串转化成代码执行------>123 *只有es5.0*
5种方法
递归的遍历每一项,若为数组则继续遍历,否则concat
function flatten(arr) {
var res = [];
arr.map(item => {
if(Array.isArray(item)) {
res = res.concat(flatten(item));
} else {
res.push(item);
}
});
return res;
}
ES6:
function flatten(arr) {
while(arr.some(item=>Array.isArray(item))) {
arr = [].concat(...arr);
}
return arr;
}
arr.some():遍历数组中每个元素,判断其是否满足指定函数的指定条件,返回true或者false
function reverseArry(arr) [
for(var i = 0;i<arr.length/2;i++){
var temp = arr[i];
arr[i] = arr[arr.length-1-i];
arr[arr.length-1-i] = temp;
}
]
利用unshift()在数组前添加元素
function reverseArry(arr) {
var result = [];
for (let i = 0; i < arr.length; i++){
result.unshift(arr[i])
}
return result;
}
function reverseArray(arr) {
var newArr = [];
for(var i = arr.length-1; i >= 0; i--){
newArr.push(arr[i]);
}
return newArr;
}
function reverseArry(arr) {
var str = arr.join(' '); //数组转字符串,空格分开
var result = [];
var word = '';
for (let i = 0; i < str.length; i++){
if (str[i] != ' '){
word += str[i]; //提取每一组字符串
}
else{
result.unshift(word); //将字符串添加到结果前
word = ''
}
}
result.unshift(word); // 最后一组字符串天剑
return result;
}
ES6 set
function unique(arr){
return Array.from(new Set(array));
}
splice(ES5)
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;
}
indexOf
function unique(arr){
if(!Array.isArray){
return false;
}
var array = [];
for(var i = 0; i < arr.length; i++){
if(arr.indexOf(arr[i) == -1){ //返回某个指定的字符串值在字符串中首次出现的位置。如果要检索的字符串值没有出现,则该方法返回 -1。
array.push(arr[i]);
}
}
return array;
}
function concat(arr1,m,arr2,n){
let len = m+n-1;
m--;
n--;
while(n>0 || m>0){
if(arr1[m]>=arr2[n]){
arr1[len]=arr1[m];
len--;
m--;
}else{
arr1[len]=arr2[n];
len--;
n--;
}
}
return arr1;
}
var a1 = [1,2,3,0,0,0];
var a2 = [2,5,6];
console.log(concat(a1,3,a2,3));
ES6 新增的一种新的数据结构,类似于数组,但成员是唯一且无序的,没有重复的值。
const s = new Set()
[1, 2, 3, 4, 3, 2, 1].forEach(x => s.add(x))
for (let i of s) {
console.log(i) // 1 2 3 4
}
// 去重数组的重复对象
let arr = [1, 2, 3, 2, 1, 1]
[... new Set(arr)] // [1, 2, 3]
属性:constructor: 构造函数。 size:元素数量
方法:set.**
let set = new Set([1, 2, 3])
console.log(set.keys()) // SetIterator {1, 2, 3}
console.log(set.values()) // SetIterator {1, 2, 3}
console.log(set.entries()) // SetIterator {1, 2, 3}
for (let item of set.keys()) {
console.log(item);
} // 1 2 3
for (let item of set.entries()) {
console.log(item);
} // [1, 1] [2, 2] [3, 3]
set.forEach((value, key) => {
console.log(key + ' : ' + value)
}) // 1 : 1 2 : 2 3 : 3
console.log([...set]) // [1, 2, 3]
let set = new Set([1, 2, 3])
set = new Set([...set].map(item => item * 2))
console.log([...set]) // [2, 4, 6]
set = new Set([...set].filter(item => (item >= 4)))
console.log([...set]) //[4, 6]
交集,并集,差集
let set1 = new Set([1, 2, 3])
let set2 = new Set([4, 3, 2])
let intersect = new Set([...set1].filter(value => set2.has(value))) //Set {2, 3}
let union = new Set([...set1, ...set2]) //Set {1, 2, 3, 4}
let difference = new Set([...set1].filter(value => !set2.has(value))) //Set {1}
var
let 和 const
因为编辑器会在判断有已经声明的同名变量时忽略var,然后直接赋值。
原理
在JS代码运行过程中:
引擎负责整个代码的编译以及运行
编译器则负责词法分析、语法分析、代码生成等工作
作用域负责维护所有的标识符(变量)。
var a = 2;
var a = 3;
a = 4;
alert(a); // 4
当我们执行上面的代码时,我们可以简单的理解为新变量分配一块儿内存,命名为a,并赋值为2,但在运行的时候编译器与引擎还会进行两项额外的操作:判断变量是否已经声明。
重复声明时:首先编译器对代码进行分析拆解,从左至右遇见var a,则编译器会询问作用域是否已经存在叫a的变量了。如果不存在,则招呼作用域声明一个新的变量a;若已经存在,则忽略 var 继续向下编译,这时 a = 2被编译成可执行的代码供引擎使用。
赋值时:引擎遇见a=2时同样会询问在当前的作用域下是否有变量a。若存在,则将a赋值为2(由于第一步编译器忽略了重复声明的var,且作用域中已经有a,所以重复声明会发生值的覆盖而不会报错);若不存在,则顺着作用域链向上查找,若最终找到了变量a则将其赋值2,若没有找到,则招呼作用域声明一个变量a并赋值为2(这就是为什么第二段代码可以正确执行且a变量为全局变量的原因,当然,在严格模式下JS会直接抛出异常:a is not defined)。
是es6中对函数一种全新的表示方法。
其中写法:1、若返回值只有一条语句,可以将{},return省略。多条不可省略。
2、参数0或多个,需要用括号。一个参数,可省略括号。
3、不能用于构造函数。
4、箭头函数的this是在定义的时候绑定,继承自父执行上下文中的this。
5、箭头函数无法使用 call()或 apply()来改变其运行的作用域。
先传递一部分参数来调用函数,然后返回一个函数去调用剩下的参数。
把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。
// 普通的add函数
function add(x, y) {
return x + y
}
// Currying后
function curryingAdd(x) {
return function (y) {
return x + y
}
}
add(1, 2) // 3
curryingAdd(1)(2) // 3
先用一个函数接收参数x,然后返回一个函数去处理参数y
优点:
参数复用
提前确认(第一参数用来判断是否进行下面操作)
延迟运行
柯里化bind
Function.prototype.bind = function (context) {
let self = this; //此时this指向 test
let arg = Array.prototype.slice.call(arguments, 1);// 去掉第一个,转换成数组
return function () {
let innerArg = Array.prototype.slice.call(arguments);// 此时arguments为传进来的参数,转换成数组
let totalArg = arg.concat(innerArg); // 拼接bind进来的参数与bind之后调用的参数 作为test的参数
return self.apply(context, totalArg);
}
}
__proto__
)属性指向构造函数的prototypenew和instanceof的内部机制
obj = new o(): obj的隐式原型链(proto)是指向o最初始的原型(prototype)的。这就是new的内部工作方式。
x instanceof y:x会一直沿着隐式原型链__proto__向上查找直到x.proto.proto…===y.prototype为止,如果找到则返回true,也就是x为y的一个实例。否则返回false,x不是y的实例。
将声明提到作用域顶部。
在生成执行环境时,会有两个阶段。
第一个阶段是创建的阶段,JS 解释器会找出需要提升的变量和函数,并且给他们提前在内存中开辟好空间,函数的话会将整个函数存入内存中,变量只声明并且赋值为 undefined 。
第二个阶段,也就是代码执行阶段,我们可以直接提前使用
箭头函数this指向不会改变。
//undefined
(()=>{console.log(this.a)}).apply({a:'word'})
构造函数必须new
call(一个一个传值)
改变this指向,借用别人的函数,实现自己的功能。
Person.call( obj );里面的 call 让 person 所有的 this 都变成 obj。
Person.call(this, name, age, sex);
apply(数组传值)
改变this指向,第2位只能传一个参数arguments。
Person.call(this, [ name, age, sex] );
let name = 'Jack'
const obj = {name: 'Tom'}
function sayHi() {console.log('Hi! ' + this.name)}
sayHi() // Hi! Jack
sayHi.call(obj) // Hi! Tom
bind(返回是函数)
const newFunc = sayHi.bind(obj)
newFunc() // Hi! Tom
caller
function test() {
demo();
}
function demo() {
demo.caller; //demo被调用的环境 -->function test(){ demo(); }
}
test();
是JavaScript语言中固有的特性,原型的原理是对象引用了另一个对象来获取该对象想的属性和方法。
prototype(原型):通过调用构造函数而创建的所有对象实例的原型对象
每个函数都有 一个prototype 属性指向一个原型对象,原型对象包含特定类型的所有实例共享的属性和方法。
每个对象(除null)都有 __proto__
属性,这个属性指向了创建该对象的构造函数的原型。而原型对象也通过__proto__
指向它自己的原型对象,层层向上直到Object.prototype,这样就形成了原型链。
原型链的特点:
当访问对象属性时,如果对象内部没有这个属性,就会沿着原型链一直往上找;
当修改原型时,与之相关的对象也会继承这一改变。
Person.prototype.name 这种.的写法是在原有的基础上把值改了。 改的是属性,也就是
房间里面的东西。
而 Person.prototype={name:’ cherry’ }是把原型改了,换了新的对象。 改了个房间。_proto_指向的空间不变。
构造函数、原型、实例之间的关系
每个实例对象( object )都有一个私有属性(称之为 __proto__
)指向它的构造函数的原型对象(prototype )。该原型对象也有一个自己的原型对象( __proto__
) ,层层向上直到一个对象的原型对象为 null
。根据定义,null
没有原型,并作为这个原型链中的最后一个环节。
每个构造函数都有一个原型对象(prototype),原型对象都包含一个指向构造函数的指针(constructor),而实例都包含一个指向原型对象的内部指针(__proto__
)。
Object.create(原型);
Object.prototype 是原型链的终端
在 JS 中,一共有两种作用域:(ES6 之前)
当代码在一个环境中执行时,会创建变量对象的一个作用域链。如果在当前作用域中没有查到值,就会沿着作用域链去上级作用域中查,直到查到全局作用域。
在内部作用域中可以访问到外部作用域的变量,在外部作用域中无法访问到内部作用域的变量。
块级作用域:ES6中新增了块级作用域,块级作用域可以通过let const声明,所声明的变量在指定块的作用域外无法被访问。块级作用域在如下情况被创建:
在一个函数内部
在一个代码块(由一对花括号包裹)内部
块级作用域的特点:
声明变量不会被提升至代码块顶部
禁止重复声明
A对象通过继承 B 对象,就能直接拥有 B 对象的所有属性和方法
先继承,后使用想要继承,就必须要提供个父类(继承谁,提供继承的属性)
重点:让新实例的原型等于父类的实例。
特点:1、实例可继承的属性有:实例的构造函数的属性,父类构造函数属性,父类原型的属性。(新实例不会继承父类实例的属性!)
缺点:1、新实例无法向父类构造函数传参。
2、继承单一。
3、所有新实例都会共享父类实例的属性。(原型上的属性是共享的,一个实例修改了原型属性,另一个实例的原型属性也会被修改!)
二、借用构造函数继承
重点:用.call()和.apply()将父类构造函数引入子类函数(在子类函数中做了父类函数的自执行(复制))
特点:1、只继承了父类构造函数的属性,没有继承父类原型的属性。
2、解决了原型链继承缺点1、2、3。
3、可以继承多个构造函数属性(call多个)。
4、在子实例中可向父实例传参。
缺点:1、只能继承父类构造函数的属性。
2、无法实现构造函数的复用。(每次用每次都要重新调用)
3、每个新实例都有父类构造函数的副本,臃肿。
三、组合继承(组合原型链继承和借用构造函数继承)(常用)
重点:结合了两种模式的优点,传参和复用
特点:1、可以继承父类原型上的属性,可以传参,可复用。
2、每个新实例引入的构造函数属性是私有的。
缺点:调用了两次父类构造函数(耗内存),子类的构造函数会代替原型上的那个父类构造函数。
四、原型式继承
重点:用一个函数包装一个对象,然后返回这个函数的调用,这个函数就变成了个可以随意增添属性的实例或对象。object.create()就是这个原理。
特点:类似于复制一个对象,用函数来包装。
缺点:1、所有实例都会继承原型上的属性。
2、无法实现复用。(新实例属性都是后面添加的)
五、寄生式继承
重点:就是给原型式继承外面套了个壳子。
优点:没有创建自定义类型,因为只是套了个壳子返回对象(这个),这个函数顺理成章就成了创建的新对象。
缺点:没用到原型,无法复用。
六、寄生组合式继承(常用)
寄生:在函数内返回对象然后调用
组合:1、函数的原型等于另一个实例。2、在函数中用apply或者call引入另一个构造函数,可传参
重点:修复了组合继承的问题
闭包是指有权访问另一个函数作用域中的变量的函数。
通常你使用只有一个方法的对象的地方,都可以使用闭包。
特点:
function makeSizer(size) {
return function() {
document.body.style.fontSize = size + 'px';
};
}
var size12 = makeSizer(12);
实现计数器
var add = (function(){
var count = 0;
return function(){
return count += 1;
}
})();
add();
用处
优点
缺点
消耗内存,参数和变量不会被垃圾回收机制回收。会造成内存溢出。
内存泄漏指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放引发的各种问题。
浅度克隆
遍历obj,将其依次放入obj1。实际上是拷贝地址,改变obj1,obj也变 。
function clone(origin, target) {
var target = target || {}; //如果没定义对象则定义
for (var prop in origin){
target[prop] = origin[prop];
}
return target;
}
clone(obj, obj1)
深度克隆
//遍历对象 for(var prop in obj)
// 1、判断是不是原始值 typeof() ? = obj
// 2、判断数组还是对象 instanceof toString constructor
// 3、建立相应的数组和对象
//递归
function deepClone(obj){
var newObj= obj instanceof Array ? []:{};
for(var item in obj){
var temple= typeof obj[item] == 'object' ? deepClone(obj[item]):obj[item];
newObj[item] = temple;
}
return newObj;
}
JavaScript代码的执行过程中,除了依靠函数调用栈来搞定函数的执行顺序以外,还依靠任务队列(task queue)来搞定另外一些代码的执行。整个执行过程,我们称为事件循环过程。一个线程中,事件循环是唯一的,但是任务队列可以拥有多个。任务队列又分为 macro-task(宏任务)和 micro-task(微任务),在最新标准中,他们分别被称为 tasks 和 jobs。
macro-task(宏任务) 大概包括:
micro-task(微任务) 大概包括:
总体结论就是:
我们知道 async
会隐式返回一个 Promise 作为结果的函数,那么可以简单理解为:await 后面的函数在执行完毕后,await 会产生一个微任务(Promise.then 是微任务)。但是我们要注意微任务产生的时机,它是执行完 await 后,直接跳出 async 函数,执行其他代码(此处就是协程的运作,A暂停执行,控制权交给B)。其他代码执行完毕后,再回到 async 函数去执行剩下的代码,然后把 await 后面的代码注册到微任务队列中。例如:
console.log('script start')
async function async1() {
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2 end')
}
async1()
setTimeout(function() {
console.log('setTimeout')
}, 0)
new Promise(resolve => {
console.log('Promise')
resolve()
}).then(function() {
console.log('promise1')
}).then(function() {
console.log('promise2')
})
console.log('script end')
// 旧版输出如下,但是请继续看完本文下面的注意那里,新版有改动
// script start => async2 end => Promise => script end => promise1 => promise2 => async1 end => setTimeout
分析这段代码:
script start
async1()
,调用了 async2()
,然后输出 async2 end
,此时会保留 async1 的上下文,然后跳出 async1script end
promise1
,然后又遇到 then,产生一个新的微任务promise2
,此时微任务队列已清空,执行权交还给 async1async1 end
setTimeout
注意
新版的 chrome 并不是像上面那样的执行顺序,它优化了 await 的执行速度,await 变得更早执行了,输出变更为:
// script start => async2 end => Promise => script end => async1 end => promise1 => promise2 => setTimeout
但是这种做法其实违反了规范,但是规范也可以更改的,这是 V8 团队的一个 PR ,目前新版打印已经修改。知乎上也有相关的 讨论 。
我们可以分两种情况进行讨论
如果 await 后面直接跟的为一个变量,比如 await 1
。这种情况相当于直接把 await 后面的代码注册为一个微任务,可以简单理解为 Promise.then(await 后面的代码)
,然后跳出函数去执行其他的代码。
如果 await 后面跟的是一个异步函数的调用,比如上面的代码修改为:
console.log('script start')
async function async1() {
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2 end')
return Promise.resolve().then(()=>{
console.log('async2 end1')
})
}
async1()
setTimeout(function() {
console.log('setTimeout')
}, 0)
new Promise(resolve => {
console.log('Promise')
resolve()
}).then(function() {
console.log('promise1')
}).then(function() {
console.log('promise2')
})
console.log('script end')
输出为:
// script start => async2 end => Promise => script end => async2 end1 => promise1 => promise2 => async1 end => setTimeout
此时 执行完 await 并不会把 await 后面的代码注册到微任务对立中,而是执行完 await 之后,直接跳出了函数,执行其他同步代码,直到其他代码执行完毕后,再回到这里将 await 后面的代码推倒微任务队列中执行。注意,此时微任务队列中是有之前注册的其他微任务,所以这种情况会先执行其他的微任务。可以理解为 await 后面的代码会在本轮循环的最后被执行。
同样是使用 V8 引擎的 Node.js 也同样有事件循环。事件循环是 Node 处理非阻塞 I / O 操作的机制,Node 中实现事件循环依赖的是 libuv 引擎。由于 Node 11 之后,事件循环的一些原理发生了改变,这里就以新的标准去讲,最后再列上变化点让大家了解前因后果。
node 中也分为宏任务和微任务,其中,
macro-task(宏任务)包括:
micro-task(微任务)包括:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mX8DyzFv-1679477731611)(null)]
图中每个框被成为事件循环机制的一个阶段,每个阶段都有一个 FIFO 队列来执行回调。虽然每个阶段都是特殊的,但是通常情况下,当事件循环进入特定的阶段时,它将执行特性该阶段的任何操作,然后执行该阶段队列中的回调,直到队列用尽或最大回调数已执行。当该队列已用尽或达到回调限制,事件循环将移动到下一阶段。
因此,上图可以简化为以下流程:
socket.on('close', ...)
日常开发中绝大部分异步任务都在 poll、check、timers 这三个阶段处理,所以需要重点了解这三个阶段
timers 阶段会执行 setTimeout 和 setInterval 回调,并且是由 poll 阶段控制的。同样,在 Node 中定时器指定的时间也不是准确时间,只是尽快执行。
poll 是一个至关重要的阶段,poll 阶段的执行逻辑流程图如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gogKX088-1679477731656)(null)]
如果当前已经存在定时器,而且有定期到时间了,拿出来执行,事件循环将会到 timers 阶段
如果没有定时器,回去看回调函数队列
check 阶段,这是一个比较简单的阶段,直接执行 setImmediate 的回调
process.nextTick
是独立于事件循环的任务队列
在每一个事件循环阶段完成后会去检查 nextTick 队列,如果里面有任务,会让这部分任务优先于微任务执行。
看一个例子:
setImmediate(() => {
console.log('timeout1')
Promise.resolve().then(() => console.log('promise resolve'))
process.nextTick(() => console.log('next tick1'))
});
setImmediate(() => {
console.log('timeout2')
process.nextTick(() => console.log('next tick2'))
});
setImmediate(() => console.log('timeout3'));
setImmediate(() => console.log('timeout4'));
在 node11 之前,因为每一个事件循环阶段完成后都会去检查 nextTick 队列,如果里面有任务,会让这部分任务优先于微任务执行,因此上述代码是先进入 check 阶段,执行所有 setImmediate,完成之后执行 nextTick 队列,最后执行微任务队列,因此输出为:
// timeout1 -> timeout2 -> timeout3 -> timeout4 -> next tick1 -> next tick2 -> promise resolve
在 node11 之后,process.nextTick
被视为是微任务的一种,因此上述代码是先进入 check 阶段,执行一个 setImmediate 宏任务,然后执行其微任务队列,在执行下一个宏任务及其微任务队列,因此输出为:
// timeout1 -> next tick1 -> promise resolve -> timeout2 -> next tick2 -> timeout3 -> timepout4
这里主要说明 node11 前后的差异,因为 node11 之后一些特性向浏览器看齐,总的变化一句话来说就是:
如果是 node11 版本一旦执行一个阶段里的一个宏任务(setTimeout、setInterval、setImmediate)就会立刻执行对应的微任务队列
setTimeout(() => {
console.log('timer1')
Promise.resolve().then(function() {
console.log('promise1')
})
}, 0)
setTimeout(() => {
console.log('timer2')
Promise.resolve().then(function() {
console.log('promise2')
})
}, 0)
如果是 node11以后的版本一旦执行到一个阶段内的一个宏任务(setTimeout、setInterval 和 setImmediate)就会立刻执行微任务队列,这和浏览器的运行方式是一致的,最后输出为:
// timer1 -> promise1 -> timer2 -> promise2
如果是 node11 之前的版本,要看第一个定时器执行完,第二个定时器是否在完成队列中
如果第二个定时器还未在完成队列中,最后的结果为
// timer1 -> promise1 -> timer2 -> promise2
如果第二个定时器已经在完成队列中,最后结果为
// timer1 -> timer2 -> promise1 -> promise2
setImmediate(() => console.log('immediate1'));
setImmediate(() => {
console.log('immediate2')
Promise.resolve().then(() => console.log('promise resolve'))
});
setImmediate(() => console.log('immediate3'));
setImmediate(() => console.log('immediate4'));
如果是 node11 后的版本,会输出
// immediate1 -> immediate2 -> promise resolve -> immediate3 -> immediate4
如果是 node11 前的版本,会输出
// immediate1 -> immediate2 -> immediate3 -> immediate4 -> promise resolve
setImmediate(() => console.log('timeout1'));
setImmediate(() => {
console.log('timeout2')
process.nextTick(() => console.log('next tick'))
});
setImmediate(() => console.log('timeout3'));
setImmediate(() => console.log('timeout4'));
如果是 node11 后的版本,会输出
// timeout1 -> timeout2 -> next tick -> timeout3 -> timeout4
如果是 node11 前的版本,会输出
// timeout1 -> timeout2 -> timeout3 -> timeout4 -> next tick
两者主要的区别在于浏览器中的微任务是在每个相应的宏任务中执行的,而 nodejs 中的微任务则是在不同阶段之间执行的。
Promise 是现代 Web 异步开发的重要组成部分,基本上目前所有的 Web 应用的异步开发手段都是通过 Promise 来实现的。
所谓 Promise,就是一个容器对象,里面保存着某个未来才会结束的事件(异步事件)的结果。Promise 是一个构造函数,它有三个特点:
在 Promise 诞生之前,Web 应用中的异步开发主要采用的是回调函数的模式(详情可以参考 node.js 各个 API),回调函数的一大缺点就是,当我们的一个异步结果需要使用另外一个异步结果时,就会产生回调嵌套,一旦这样的嵌套多了,就会变成回调地狱,十分影响代码观感。
而 Promise 的诞生一定程度上解决了这个问题,因为 Promise 是采用链式调用的方式,并且在 Promise 返回的 Promise 对象中的 then、catch 等一系列方法都会返回一个新的 Promise 对象。
当 Promise 实例创建成功后,可以执行其原型上的 then 方法。then 方法接受两个参数,分别是当前 Promise 的成功回调和失败回调,并且这两个函数会自动返回一个新的 Promise 对象。
then 方法的成功回调如果返回的是一个 Promise 对象,那么只有当这个 Promise 对象状态发生改变之后,才会执行下一个 then。
Promise 原型上还有一个 catch 方法,它会捕获在它链式之前的所有未被捕获的错误(一旦前面的错误被捕获,就不会执行)。
catch 只是捕获异常,catch 并不能终止当前 Promise 的链式调用。
同样的,catch 方法也会自动返回一个新的 Promise 对象,一旦显示返回一个 Promise,那么只有当这个 Promise 对象状态发生改变时,链式调用才会继续往下走。
在 ES8 中新加入的方法,此方法和 then、catch 不同,它不会跟踪 Promise 的状态,即不管 Promise 最终变成了什么状态,都会执行这个方法,同时 finally 不接收任何参数。
同样的,finally 并不是链式调用的终点,它也会自动返回一个新的 Promise 对象。
上述三个 API 是组成 Promise 的最基本的 API,除了这三个 API 以外,还有很多其他 API。
这个方法同字面意思一样,参数接收一个 Promise 数组,返回一个新的 Promise。只有当数组中的每一个元素都成功之后,Promise 的状态才会变更为 fulfilled,否则只要有一个失败,状态都会变更为 rejected。
成功之后的结果同样是返回一个数组,里面的每个元素按顺序对应着传入的 Promise 数组。
字面意思是竞速,参数同样是接收一个 Promise 数组,返回一个新的 Promise。数组中无论哪个元素先发生状态改变,结果就返回先完成的那个 Promise 的值。
该方法是 ES2020 新加入的方法,参数和返回值同 Promise.all
一样,区别在于该方法返回值不论元素是否成功,只要执行结束,则返回最终执行的结果。
由于回调表达异步流程的方式是非线性的、非顺序的,导致代码调试困难。我们需要一种更同步、更顺序、更阻塞的的方式来表达异步,就像我们的大脑一样。
例如: ajax多层嵌套函数,很难处理bug
ajax('XXX1', () => {
// callback 函数体
ajax('XXX2', () => {
// callback 函数体
ajax('XXX3', () => {
// callback 函数体
})
})
})
回调函数:因为javascript是单线程的,所以有些需要等待的地方,需要使用回调函数。
回调地狱:由于某些业务的问题,在代码中会一次性写会多层的回调嵌套,回调嵌套后的代码的维护难度,和无法排除bug。这个就被称作回调地狱。
回调地狱带来的负面作用有以下几点:
如何解决:promise、async/await
Promise就是为了解决callback的问题而产生的。
1. promise ES6 原生
Promise是一个对象,用来传递异步操作的消息。可以将异步操作队列化,按照期望的顺序执行,返回符合预期的结果,可以在对象之间传递和操作promise,帮助我们处理队列。对于开发这种多层嵌套的代码很方便,降低了代码的维护难度等等。
优点:避免层层嵌套的回调函数(js是单线程,需要等待的地方会使用回调函数),解决了回调地狱的问题。
缺点:无法取消 Promise ,错误需要通过回调函数来捕获
ES6将事情划分为三种状态:pending,resolved,rejected
ajax('XXX1')
.then(res => {
// 操作逻辑
return ajax('XXX2')
}).then(res => {
// 操作逻辑
return ajax('XXX3')
}).then(res => {
// 操作逻辑
})
2. generator
生成器内部的代码是以自然的同步 / 顺序方式表达任务的一系列步骤
function *fetch() {
yield ajax('XXX1', () => {})
yield ajax('XXX2', () => {})
yield ajax('XXX3', () => {})
}
let it = fetch()
let result1 = it.next()
let result2 = it.next()
let result3 = it.next()
3. async/await
目的是简化 Promise api 的使用,并非是替代 Promise。
优点:代码清晰,不用像 Promise 写一大堆 then 链,处理了回调地狱的问题
缺点:await 将异步代码改造成同步代码,如果多个异步操作没有依赖性而使用 await 会导致性能上的降低。
async function test() {
// 以下代码没有依赖性的话,完全可以使用 Promise.all 的方式
// 如果有依赖性的话,其实就是解决回调地狱的例子了
await fetch('XXX1')
await fetch('XXX2')
await fetch('XXX3')
}
https://juejin.im/post/6844903882083024910
客户端在向服务器发送ajax请求的时候会有跨域的限制,
绕过同源策略去获取数据
广义跨域:
资源跳转: A链接、重定向、表单提交
资源嵌入: <\link>、<\script>、<\img>、<\frame>等dom标签,还有样式background:url()、@font-face()等文件外链
脚本请求: js发起的ajax请求、dom和js对象的跨域操作等
同源策略是浏览器上为安全性考虑实施的非常重要的安全策略。
同源:URL的协议、域名和端口相同
。
只允许与本域下的接口交互。目的:避免恶意脚本盗号等。
同源策略的限制:
允许用户传递一个callback参数给服务端,然后服务端返回数据时会将这个callback参数作为函数名来包裹住JSON数据,这样客户端就可以随意定制自己的函数来自动处理返回数据了。
HTML 中 script,img标签这样获取资源的标签是没有跨域限制的。
imghttps://blog.csdn.net/weixin_34392843/article/details/91367766
jsonp的原理就是利用标签没有跨域限制,通过
标签src属性,发送带有callback参数的GET请求,服务端将接口返回数据拼凑到callback函数中,返回给浏览器,浏览器解析执行,从而前端拿到callback函数返回的数据。
服务端返回如下(返回时即执行全局函数):
handleCallback({"success": true, "user": "admin"})
ajax的核心是通过XmlHttpRequest获取非本页内容,而jsonp的核心则是动态添加。
转载:https://blog.csdn.net/hansexploration/article/details/80314948
Cross-Origin Resource Sharing(跨域资源共享 ),是一种 ajax 跨域请求资源的方式。
简单请求:
简单跨域请求只需服务器端设置Access-Control-Allow-Origin,带cookie请求的前后端都要设置。
CORS进阶之cookie处理
为了安全,默认情况下,浏览器的cookie也是不能作为信息传递给跨域的服务器的。
10.107.98.46:8089/select/test.html跨域到10.107.98.46:8088/AngularTodoMVC/index.html。
第一步,要做的是首先浏览器上有cookie。
先设置10.107.98.46:8089/select/test.html对应的nginx
add_header 'Set-Cookie' 'name=value';设置cookie信息的,键为name,值为value。
第一步,是要测试把cookie发送给跨域的服务器。
要发送带cookie的请求到跨域服务器中,只需要把withCredentials设为true即可。
var xhttp = new XMLHttpRequest();
xhttp.open("get", "http://10.107.98.46:8088/AngularTodoMVC/index.html", true);
xhttp.withCredentials=true
xhttp.send();
服务器端设置Access-Control-Allow-Credentials
add_header 'Access-Control-Allow-Origin' 'http://10.107.98.46:8089';
add_header 'Access-Control-Allow-Credentials' 'true';
echo "name = $cookie_name";服务器上获得cookie的信息
相比于JSONP的优势
JSONP只支持GET请求,CORS支持所有类型的HTTP请求。JSONP的优势在于支持老式浏览器,以及可以向不支持CORS的网站请求数据
扩展见:http://www.ruanyifeng.com/blog/2016/04/cors.html
将静态资源放在一个域名下。减少DNS解析的开销。
将静态资源放在多个子域名下,就可以多线程下载,提高并行度,使客户端加载静态资源更加迅速。
域名发散是pc端为了利用浏览器的多线程并行下载能力。
域名收敛多用与移动端,提高性能,因为dns解析是是从后向前迭代解析,如果域名过多性能会下降,增加DNS的解析开销。
event.target:返回触发事件的元素
event.currentTarget:返回绑定事件的元素
事件处理函数中的this指向是中为currentTarget
currentTarget和target,有时候是同一个元素,有时候不是同一个元素 (因为事件冒泡)
<button onclick="func()">按钮</button>
btn.onclick = function(){}
btn.addEventListener('click',function(){})
//解绑
btn.removeEventListener('click', function(){})
延伸:DOM3事件
1. UI事件:当用户与页面上的元素交互时触发,如:load、scroll 焦点事件:当元素获得或失去焦点时触发,如:blur、focus
2. 鼠标事件:当用户通过鼠标在页面执行操作时触发如:dbclick、mouseup
3. 滚轮事件:当使用鼠标滚轮或类似设备时触发,如:mousewheel 文本事件:当在文档中输入文本时触发,如:textInput
4. 键盘事件:当用户通过键盘在页面上执行操作时触发,如:keydown、keypress
5. 合成事件:当为IME(输入法编辑器)输入字符时触发,如:compositionstart
6. 变动事件:当底层DOM结构发生变化时触发,如:DOMsubtreeModified
事件流描述的是从页面中接收事件的顺序
DOM2事件流:
结构上(非视觉上)嵌套关系的元素,会存在事件冒泡的功能,即同一事件,自子元素冒泡向父元素触发。(自底向上)
阻止冒泡:
W3C:stopPropagation()
IE:cancelBubble = true;
<div class="wrapper">
<div class="content">
<div class="box"></div>
</div>
</div>
<script>
var wrapper = document.getElementsByClassName('wrapper')[0];
var content = document.getElementsByClassName('content')[0];
var box = document.getElementsByClassName('box')[0];
//都是click事件
wrapper.addEventListener('click', function () {
console.log('wrapper')
},false);
content.addEventListener('click', function () {
console.log('content')
},false);
box.addEventListener('click', function () {
console.log('box')
},false);
</script>
父级元素先触发,子级元素后触发。(与冒泡相反)
wrapper,content,box
阻止捕获:
W3C:preventDefault()
IE:window.event.returnValue = false
利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。具体只需把原本需要绑定的事件委托给父元素,让父元素担当事件监听的职务。(鼠标事件,键盘事件)
<div class="all">
<div class="box">box1</div>
<div class="box">box2</div>
<div class="box">box3</div>
</div>
document.querySelector('.all').onclick = (event) => {
let target = event.target
if (target.className === 'box') {
console.log(target.innerHTML)
}
}
事件循环是一个单线程循环,用于监视调用堆栈并检查是否有工作即将在任务队列中完成。如果调用堆栈为空并且任务队列中有回调函数,则将回调函数出队并推送到调用堆栈中执行。
MDN
var event = new Event('build');
// Listen for the event.
elem.addEventListener('build', function (e) { ... }, false);
// Dispatch the event.
elem.dispatchEvent(event);
mouseover:当鼠标移入元素或其子元素都会触发事件,所以有一个重复触发,冒泡的过程。对应的移除事件是mouseout
mouseenter:当鼠标移除元素本身(不包含元素的子元素)会触发事件,也就是不会冒泡,对应的移除事件是mouseleave
给需要拖拽的节点绑定 mousedown , mousemove , mouseup 事件
mousedown 事件触发后,开始拖拽
mousemove 时,需要通过 event.clientX 和 clientY 获取拖拽位置,并实时更新位置
mouseup 时,拖拽结束
需要注意浏览器边界的情况
var X= ele.getBoundingClientRect().left;
var Y =ele.getBoundingClientRect().top;
//再加上滚动距离,就可以得到绝对位置
var X= ele.getBoundingClientRect().left+document.documentElement.scrollLeft;
var Y =ele.getBoundingClientRect().top+document.documentElement.scrollTop;
防抖:函数频繁触发,当停止触发后空闲一段时间后执行一次。
function debounce(fn, interval = 300) {
let timeout = null;
return function () {
clearTimeout(timeout);
timeout = setTimeout(() => {
fn.apply(this, arguments);
}, interval);
};
}
节流:指定时间间隔内只会执行一次任务。
function throttle(fn, interval = 300) {
let canRun = true;
return function () {
if (!canRun) return;
canRun = false;
setTimeout(() => {
fn.apply(this, arguments);
canRun = true; //可以执行下一次的循环了
}, interval);
};
}
get:用户获取数据,可以不用每次都与数据库连接,所以可以使用缓存。
post:用于修改和删除的工作,所以必须与数据库交互,所以不能使用缓存。因此get请求适合于请求缓存。
Cookie
Cookie 是小甜饼的意思。顾名思义,cookie 确实非常小,它的大小限制为4KB左右。它的主要用途有保存登录信息,比如你登录某个网站市场可以看到“记住密码”,这通常就是通过在 Cookie 中存入一段辨别用户身份的数据来实现的。
localStorage
localStorage 是 HTML5 标准中新加入的技术,它并不是什么划时代的新东西。早在 IE 6 时代,就有一个叫 userData 的东西用于本地存储,而当时考虑到浏览器兼容性,更通用的方案是使用 Flash。而如今,localStorage 被大多数浏览器所支持,如果你的网站需要支持 IE6+,那以 userData 作为你的 polyfill 的方案是种不错的选择。
sessionStorage
sessionStorage 与 localStorage 的接口类似,但保存数据的生命周期与 localStorage 不同。做过后端开发的同学应该知道 Session 这个词的意思,直译过来是“会话”。而 sessionStorage 是一个前端的概念,它只是可以将一部分数据在当前会话中保存下来,刷新页面数据依旧存在。但当页面关闭后,sessionStorage 中的数据就会被清空。
当一个元素视觉表现属性改变时,会触发重绘。例如元素背景颜色的改变、字体颜色的改变、边框颜色的改变、透明度的改变等。
当渲染树的一部分或全部更新而导致网页结构或节点尺寸发生改变时,都会导致重排。例如可见元素节点的添加和删除、改变元素的尺寸(元素宽、高、内边距大小、边框大小)、浏览器窗口大小发生改变等。
概念:
通过渲染树中渲染对象的信息,计算出每个渲染对象的位置和尺寸,将其安排在浏览器窗口的正确位置,而有些时候我们会在文档布局完成后对文档布局进行修改,这时候可可能要重新布局,称其“回流”。
每个页面至少需要一次回流,就是在页面第一次加载的时候。
重排一定会重绘
1.改变样式时:
可以使用cssText进行优化 :
//覆盖
element.style.cssText = "border-left:1px;border-right:2px;border-bottom:3px;";
//添加
element.style.cssText += ";border-left:1px;border-right:2px;border-bottom:3px;";
2.批量修改DOM:
(1)使元素脱离文档流(2次重排)
① display:block/none(页面会闪动)
② 使用文档片段(document fragement)在当前DOM之外构建一个子树,再把它拷贝回文档
let fragment = document.createDocumentFragment();
//这里对fragment进行一些节点添加操作
document.querySelector("#mytable").appendChild(fragment);
上述代码触发了一次重排并且只访问了一次DOM
③ 为需要的节点创建一个备份(cloneNode(true),拷贝所有后代,表示深拷贝),然后对副本进行操作,操作完成后,就用新的节点替代旧的节点。
let old = document.querySelector("#mylist");
let clone = old.cloneNode(true);//拷贝所有后代
//这里对clone这个节点进行若干操作
old.parentNode.replaceChild(clone,old);//通过old的父节点找到old然后将old替换成clone
(2)对其应用多重改变。(1次重排)
(3)把元素带回文档中(2次重排)
预加载:提前加载图片,当用户需要查看时可直接从本地缓存中渲染。
懒加载:懒加载的主要目的是作为服务器前端的优化,减少请求数或延迟请求数。
两种技术的本质:两者的行为是相反的,一个是提前加载,一个是迟缓甚至不加载。
懒加载对服务器前端有一定的缓解压力作用,预加载则会增加服务器前端压力。
var packet = file.slice(start, end);
没有被应用的对象就是垃圾,就要被清除。
若几个对象形成一个环,但是根访问不到,也是垃圾。
由于字符串、对象和数组没有固定大小,所有当他们的大小已知时,才能对他们进行动态的存储分配。JavaScript程序每次创建字符串、数组或对象时,解释器都必须分配内存来存储那个实体。只要像这样动态地分配了内存,最终都要释放这些内存以便他们能够被再用,否则,JavaScript的解释器将会消耗完系统中所有可用的内存,造成系统崩溃。
JavaScript的解释器可以检测到何时程序不再需要这个对象,可以把它所占用的内存释放掉了。
var a="hello world";
var b="world";
var a=b; //会释放掉"hello world"
用JavaScript 对象结构表示 DOM 树的结构;然后用这个树构建一个真正的 DOM 树,插到文档当中。
当状态变更的时候
webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个bundle。
JS有一个任务队列,按顺序执行。setTimeout(fn,1000),1000毫秒后将fn添加到任务队列(若队列为空,立即执行)。
var ID = setTimeout(function(){ //调用setTimeout()之后,该方法会返回一个数值ID,表示超时调用
alert("run");
},1000)
clearTimeout(ID); //取消超时调用
封装一个函数,参数是定时器的时间,.then执行回调函数。
function sleep(time){
return new Promise((resolve)=>setTimeout(resolve,time));
}
和setTimeout类似,只是要按照指定的时间间隔重复执行代码,直至间歇调用被取消或页面被卸载。
var ID = setInterval(function(){
alert("run");
},1000)
clearInterval(ID);
"Event Loop是一个程序结构,用于等待和发送消息和事件。
macro-task(宏任务):包括整体代码script,setTimeout,setInterval
micro-task(微任务):Promise,process.nextTick
console.log('script start')
const interval = setInterval(() => { //interval
console.log('setInterval')
}, 0)
setTimeout(() => { //timeout1
console.log('setTimeout 1')
Promise.resolve() //then2
.then(() => console.log('promise 3'))
.then(() => console.log('promise 4'))
.then(() => {
setTimeout(() => { //timeout2
console.log('setTimeout 2')
Promise.resolve().then(() => console.log('promise 5')) //then3
.then(() => console.log('promise 6'))
.then(() => clearInterval(interval))
}, 0)
})
}, 0)
Promise.resolve() //then1
.then(() => console.log('promise 1'))
.then(() => console.log('promise 2'))
宏任务 | 微任务 |
---|---|
interval | then1 |
timeout1 |
promise1, promise2
宏任务 | 微任务 |
---|---|
timeout1 | |
interval |
宏任务 | 微任务 |
---|---|
interval | then2 |
interval | |
timeout2 |
promise 3, promise 4
宏任务 | 微任务 |
---|---|
timeout2 |
宏任务 | 微任务 |
---|---|
then3 |
promise 5, promise 6
https://www.jb51.net/article/166579.htm
https://www.jdon.com/idea/js/es5-es6.html
https://www.cnblogs.com/sunshinezjb/p/9248533.html