title: JavaScript
tags: JavaScript
notebook:JavaScript笔记整理
1.JavaScript基础
2022 年 3 月 18 日
-
大数据类型使用 bigInt
-
null 有数据位置但是没有数据,undefined 是完全没有数据位置
-
object{} 对象,array[]数组
-
if 中如果没有写判断语句则括号中的内容会被转换成布尔类型(隐式转换)
- false : null nuderfined 0 " " NaN
-
switch 的 key 和 val 的比较是===绝对相等
-
switch 中的 break 用来中断程序
-
&&与,||或,!非
-
语句和表达式的区别
- 语句:声明的 、 if 、 for 、 switch
- 表达式: 三元 、 函数执行
-
&&有一个是 flash 就是 flash,前面成立将值付给后面
-
|| 有一个是 true 就是 true ,前面不成立将值给后面
-
使用 ID 属性获取元素标签,用 document.getElementById(“标签的 ID 属性名”)
-
使用 class 属性获取元素标签,使用 document.getElementsClassName(“标签的 Class 属性名”)[第几个标签元素]
-
使用元素标签选择器,使用 document.getElementsTagName(“元素标签名”)[第几个元素标签"]
-
使用 querySelector 选择标签,使用方法用 document.querySeletor(用 class 名称)
-
使用 querySelectorAll 选择标签,使用方法使用 document.querySeletorAll(用 class 名称),与没有 All 不同的是这个选择的是数组
-
获取元素内部结构,用选择器选择出来标签,在用 innerHtml 可以拿到标签内部的标签,获取的时候能够获取结构,设置的时候能够识别结构
-
获取元素内部的文本,用选择器选择出来标签,在用 innerText 可以拿到标签内部的文本
-
给元素标签设置样式:’选择的标签属性.style.需要设置的样式名称 ’
-
查看标签样式,使用方法 style 但是只能查找到行内设置的样式
-
设置标签的类名,使用 className 可以获取和谁知标签的类名
-
值类型的赋予相当于是拷贝,引用类型等于指向(地址)
-
classList 查看类名列表,插入类名使用:类名.classList.add (“类名”)
-
移除类名使用:类名.classList.remove (“类名”)
-
在类名中移除或者添加使用:获取的标签.classLista.toggle(“类名”)
-
点击实践用 onclick:使用方法:选中的元素.onclick = function(){}
-
使用鼠标滑入功能 onmouseenter,鼠标滑出功能 onmouseleve
2022 年 3 月 19 日
- 创建一个函数 function(){}
- 创建函数的关键字是 function,括号里面是形参
- 形参是形式上的参数,在 function()括号中的参数是形参,函数执行的时候使用的形参叫做实参
- function(参数 1,参数 2,参数 3,参数 4……)
//对于函数体来说,形参就是变量
函数名(实参 1,实参 2,实参 3,实参 4……)实参和形参是按照位置一一对应的;若没有实参和形参对应的默认值就是 undefined - 函数后面 return 后面跟的值就是这个函数的执行结果,不写 return 默认的执行结果就是 undefined
- 函数的书写方法:
- function 函数名(){}
- var 函数名 = function(){}
- 形参和实参
- 返回值 return
- this 是实践绑定的集合
- 实参集合 arguments,实参集合是类数组,只要是函数就有 arguments
[这是 md 文件的使用方法] (https://www.cnblogs.com/liugang-vip/p/6337580.html) - this 和 arguments 都是函数中的特殊存在
- 提高代码的容错率,可以在传入实参使用||,当没有传入实参也保证代码可以正常使用
- es6 中使用 function arr(形参=10){},给函数 arr 的形参赋予默认值设置为 10,但是如果调用函数 arr(null)是不出的及结果形参!= 10
- es5 中使用形参默认值:function 函数名(){形参 = 形参 || 100}
复习
- 基本数据类型 string number boolean null symbol bigInt
- ‘’ " "
- number 12345.243 NaN tofix(2) /保护两位小数/
- isNaN(“q123”)判断括号的内容是否为 NaN,NaN != NaN true
- false : 0 ‘’ null undefined NaN
- {} [] /\d/ function
- 对象的增删改查:var obj = {} ; obj.qq = 123 ; obj[9] = 123; obj[‘qq’] = 88 ; delete obj.qq /删除 obj 中的 qq 数据/ ; obj.null = null /假删/
- 值类型属于复制,如果值类型引用,当更改其中一个值引用值是不会发生变化的;引用类型扥于是指向同一地址,如果进行更改则会将地址中的内容更改
- 判断数据类型:typeof instanceof constructior object.prototype.tostring.call()
- if switch for 三元 while do while
- let a = a || w ;前面成立给前面
- let a = a && w ; 前面成立给后面
2022 年 3 月 21 日
- 引用数据类型操作的是地址,原始数据类型操作的是值。
- 一个变量不能指向多个地址,一个地址可以被多个地址指向。
- 函数字面量方式
function a(){}
,函数表达式方式var a = function(){}
- 函数由函数名、形参、实参、函数体、return 组成
- 自执行函数:
(function (){})()
-
(function(){ alert("自执行函数"); })();
-
(()=>{ alert("自执行函数"); })();
-
- 箭头函数是 es6 新增内容:
- 如果形参只有一个,那小括号可省略
- 如果{} return 只有一条语句,大括号和 return 可以省略
var result = index => index + 1; var result = function(index) { return index + 1; }
- 箭头函数中没有 arguments 实参列表
- caller 是指当前函数在哪里被调用指向的就是调用的函数(宿主环境),callee 指向当前元素本生
var result = function(index) { inner(); console.log(this.celler) //指向的宿主环境是document return index + 1; } var inner = function(){ console.log(inner.caller); //指向的是result函数 console.log(arguments.callee); //指向形参列表所在的函数inner } result();
2.数组操作
- 数组(Array)是一个有序的集合,用逗号隔开的每一个值就是一个元素、
- 每一个元素都有自己的位置,叫下标也叫索引(0~n),读取数组可以通过下标
- 数组有一个内置的属性 length,属性值存在的是数组的元素个数(长度)
for(var i =0 ; i < array.length ; i++){ console.log("这是第"i"个数组:"li[i]); //依次打印出来数组的下标及每个数组元素 }
- 给数组最后一位添加一个数字
array[array.length] = "需要添加的数值"
- 数组循环 for in 循环
for(var key in arr){ console.log(arr[key]); //依次输出数组arr的每一个元素 }
- 多维数组:array[2][2]三行三列
array = [['1','2','3'], ['1','2','3'], ['1','2','3']]
- push()
- 作用:在一个数组的末尾追加元素
- 参数:追加的元素
- 返回值:追加数组的长度
- 是否改变原数组:是
var colors = ['red', 'pink'];
var res = colors.push('blue');
// 原数组
console.log(colors);//['red', 'pink', 'blue']
// 返回值
console.log(res);//
- pop()
- 作用:删除末尾的最后一个
- 参数:没有
- 返回值:被删除的元素
- 是否改变原数组:是
var colors = ['red', 'pink'];
var res = colors.pop();
// 原数组
console.log(colors);//['red']
// 返回值
console.log(res);//pink
- shift()
- 作用:删除数组的第一项
- 参数:没有
- 返回值:被删除的元素
- 是否改变原数组:是
var colors = ['red', 'pink'];
var res = colors.shift();
// 原数组
console.log(colors);//['pink']
// 返回值
console.log(res);//red
- unshift()
- 作用:在数组前面追加元素
- 参数:追加的东西
- 返回值:追加数组的长度
- 是否改变原数组:是
var colors = ['red', 'pink'];
var res = colors.unshift('blue', 'green');
// 原数组
console.log(colors);//['blue', 'green', 'red', 'pink']
// 返回值
console.log(res);//
- reverse()
- 作用:把数组的每一项翻转
- 参数:没有
- 返回值:一个新数组
- 是否改变原数组:是
var ary=[1,2,3];
var res=ary.reverse();
console.log(res);//[3,2,1];
console.log(ary);//[3,2,1];
- sort()
- 作用:排序
- 参数:没有
- 返回值:一个新数组(排完后)
- 是否改变原数组:是
- 如果没有传参数,只能对 10 以内的数进行排序
- 需要在 sort()括号内传入一个函数(回调函数)进行判断排序
var oldAry = [3, 1, 5, 11, 20];
var returnValue = oldAry.sort((a,b)=>a-b)
console.log(oldAry, returnValue);
- splice()
- 作用:怎删改一体化
- 参数:多个
- 返回值:删除的元素(返回值是一个数组)
- 是否改变原数组:是
- 删除必须要有 2 个参数
- 第一个参数 表示你要开始删除的下标
- 第二个参数 表示你要删除的个数
var nums = [1, 2, 3, 4, 5, 6]; var res = nums.splice(0, 2); console.log(nums);// [3, 4, 5, 6] console.log(res);// [1,2]
- 增加需要传递三个参数
- 第一个参数 表示你要开始删除的下标
- 第二个参数 表示你要删除的个数
- 第三个参数 表示你要新增的元素
var nums = [1, 2, 3, 4, 5, 6]; var res = nums.splice(3, 0, 7, 8, 9); console.log(nums);//[1, 2, 3, 7, 8, 9, 4, 5, 6] console.log(res);// []
- 修改需要传递三个参数
- 修改也需要传递 3 个参数
- 第一个参数:开始的位置
- 第二个参数:要删除的元素数量
- 第三个参数: 要插入的元素(任意个)
var nums = [1, 2, 3, 4, 5, 6]; var res = nums.splice(0, 3, 7, 8, 9); console.log(nums);//[7, 8, 9, 4, 5, 6] console.log(res);// [1, 2, 3]
- slice()
- 作用:截取你选中的特定的内容
- 参数:1 个或者 2 个
- 返回值:截取的数组
- 是否改变原数组:否
如果不传参数或者参数只有一个(0),表示将原数组复制一遍
- 如果只传入一个参数,slice()会返回该索引到数组末尾的所有元素
var nums = [1, 2, 3, 4, 5, 6];
var res = nums.slice(1);
console.log(nums);//[1, 2, 3, 4, 5, 6]
console.log(res);// [2, 3, 4, 5, 6]
2. 如果有两个参数,slice()返回从开始索引到结束索引对应的所有元素
> 两个参数 表示开始下标到结束下标,包含开始,不包含结束
> 如果开始的位置大于结束的位置,返回值是空数组没有返回值
var nums = [1, 2, 3, 4, 5, 6];
var res = nums.slice(1, 5);
console.log(nums);//[1, 2, 3, 4, 5, 6]
console.log(res);// [2, 3, 4, 5]
-
concat()
- 作用:实现多个数组或者值得凭借
- 参数:数组或者值
- 返回值:返回值是拼接后的新数组
- 是否改变原数组:否
var ary1 = [1, 2, 3];
var ary2 = [4, 5, 6];
var res = ary1.concat(ary2, "学校", "同学");
console.log(ary1);//[1, 2, 3]
console.log(res);//[1, 2, 3, 4, 5, 6, '学校', '同学']
-
toString()
- 作用:把数组转化成字符串
- 参数:没有
- 返回值:字符串
- 是否改变原数组:否
var ary1 = [1, { a: 1 }, null, undefined, 3];
var res = ary1.toString();
console.log(ary1);//[1, {a:1},null, undefined, 3]
console.log(res)//1,[object Object]
-
join()
- 作用:用指定的字符凭借数组
- 参数:字符
- 返回值:拼接完的字符串
- 是否改变原数组:否
var ary1 = [1, 2, undefined, 3, { a: 1 }];
var res = ary1.join("|");
console.log(ary1);//[1, 2, undefined, 3, { a: 1 }]
console.log(res)// 1|2||3|[object Object]
eval(res) //==> eval 执行计算
-
includes()
- 作用:检测数组中是否包含某一项
- 参数:具体项
- 返回值:布尔值
- 是否改变原数组:否
2022 年 3 月 22 日
- indexOf/lastIndexOf
- 作用:获取某项在数组中(首次出现/最后出现的) 索引(也可以用来是否包含某项)
- 参数:(n,m)
- 返回值:-1 或者具体的索引值
- 是否改变原数组:否
- indexOf(第一次出现)
- 第一个参数 你要检索的项
- 第二个参数 从哪里开始检索
var ary = [1,2,3,1,1,2,3]; var returnVlue = ary.indexOf(1,2); console.log(returnVlue,ary);//3,原数组
- lastIndexOf(最后一次出现的位置)
- 第一个参数 你要检测的项
- 第二个参数 到那个位置结束检索
var ary = [1,2,3,1,1,2,3]; var returnVlue = ary.lastIndexOf(1,2); console.log(returnVlue,ary);//4,原数组
- 数组的迭代方法
- forEach 遍历数组
- 括号中传入函数,函数由三个形参,第一形参是数组的每一项的数据,第二项是数组下标,第三项是原数组
var ary = [1,2,3,1,1,2,3]; ary.forEach(function(item,value){ console.log(item,value); })
- map 遍历数组方法
- 和 forEach 使用方法相同,但是它有返回值,它映射了一个新数组,内容里面要写 return
var ary = [1,2,3,1,1,2,3]; var res = ary.map(function(item,value){ return item + 1; }) console.log(res) //[2,3,4,2,2,3,4]
- forEach 遍历数组
算法值数组去重
-
数组去重会出现下标塌陷问题
var ary = [1, 2, 3, 1, 1, 2, 3]; for (var i = 0; i < ary.length - 1; i++) { for (var j = i + 1; j < ary.length; j++) { if (ary[i] == ary[j]) { ary.splice(j, 1); j--; //解决下标塌陷问题,删除重复数组后将下标前移 } } } console.log(ary);
var ary = [1, 2, 3, 43, 2, 1, 2, 3, 4, 11, 1, 1, 1, 1, 1, 1]; var obj = {}; for (var i = 0; i < ary.length; i++) { var key = ary[i]; if (typeof obj[key] == "undefined") { obj[key] = ary[i]; } else { ary.splice(i, 1); i--; } } console.log(ary);
var ary = [1, 2, 3, 43, 2, 1, 2, 3, 4,11,1,1,1,1,1,1]; var obj = {}; var ary1 = []; for (var i = 0; i < ary.length; i++) { var key = ary[i]; obj[key] = ary[i]; //对象的属性名不能重复的特性 } for(var i in obj){ ary1.push(obj[i]); } console.log(ary1);
var ary = [1,2,3,4,2,1,3,4,5,6]; var newAry = []; for(var i in ary){ var item = ary[i]; if(newAry.indexOf(item) == -1){ newAry.push(ary[i]); } } console.log(newAry);
算法值冒泡排序
-
var ray = [1, 4, 3, 2, 6, 5, 7]; var npm; for (var i = 0; i < ray.length - 1; i++) { for (var j = 0; j < ray.length - 1 - i; j++){ if(ray[j] > ray[j+1]){ npm = ray[i]; ray[i] = ray[j]; ray[j] = npm; } } } console.log(ray);
递归
-
function total(num) { if (num > 100) return 0; return num + total(num + 1); } console.log(total(1));
3.字符串操作
2022 年 3 月 23 日
- charAt()
- 作用:通过下标找到对应的字符串
var sty = "aewewerrer"; console.log(sty.charAt(0)); //a
- charCodeAt()
- 作用:通过下标找到对应字符的 ASCll
var sty = "aewewerrer"; console.log(sty.charCodeAt(0)); //97
- indexOf()
- 作用:查找字符串中的元素下标,第一参数是你要查找的值,第二个值是你要限定的范围:indexOf 表示从这个下标开始,lastIndexOf 表示查找最后一次出现的位置
var sty = "aewewerrer"; console.log(sty.indexOf("e"));//1 console.log(sty.lastIndexOf("e"));//8
- slice()
- 作用:截取字符串,参数(m,n)包含 m,不包含 n;如果参数中有负数,用 length 加完后在处理
var sty = "aewewerrer"; console.log(sty.slice(3, 5));
- substring(n,m)
- 作用:和 slice 一样但是它不支持负数索引
- substr()
- 作用:第一个下标就是就是开始的位置,第二个参数表示截取几个
var sty = "aewewerrer"; console.log(sty.substr(3, 4));//ewer
- toUpperCase()
- 作用:是把字符全部转化为大写
- toLowerCase()
- 作用:把字符全部转化为小写
- replace()
- 作用:替换掉数组中的相应字符替换掉,只能换掉第一个(一般配合正则使用)
var sty = "aewewerrer"; console.log(sty.replace("aew", "yyy")); //yyyewerrer
- split()
-
作用:用符号将字符串分割成数组
var sty = "aewewerrer"; console.log(sty.split("")); //[a,e,w,e,w,e,r,r,e,r]
- 网址拆解
var str = "https://www.baidu.com/s?tn=44004473_41_oem_dg&ie=utf-8&wd=%E5%B0%8F%E7%B1%B3%E5%AE%98%E7%BD%91";
function preat(str) {
var strSlice = str.split("?");
var strArry = strSlice[1].split("&");
var obj = {};
for (var i = 0; i < strArry.length; i++) {
strArry[i] = strArry[i].split("=");
obj[strArry[i][0]] = strArry[i][1];
}
return obj;
}
console.log(preat(str));
Math 的常用方法
-
Math.abs():绝对值
console.log(Math.abs(-1)); // 1
-
Math.floor()向下取整,无论是正数还是负数都取小的
console.log(Math.floor(1.7));// 1
-
Math.ceil()向上取整,无论是正数还是负数取的都是大的
console.log(Math.floor(1.7)); // 2
-
Math.round()对小数四舍五入
console.log(Math.round(1.6)); // 2
-
Math.sqrt()开平方
console.log(Math.sqrt(9)); // 3
-
Math.pow(n,m)取幂
-
Math.PI()圆周率 π
-
Math.min()取最小值
-
Math.max()取最大值
-
math.random()取 0 到 1 不包含 1 的随机数
- Math.random()*(m-n)+n;//m:最大的值,n:最小的值,取随机的 n-m 之间的数
-
输出水仙花数
for (var i = 101; i < 1000; i++) {
var a = i % 10;
var b = Math.floor(i / 10 % 10);
var c = Math.floor(i / 100);
if (i == a * a * a + b * b * b + c * c * c) {
console.log(i)
}
}
2022 年 3 月 25 日
- 箭头函数虽然没有 arguments 这个关键字:但是可以通过…剩余函数运算来实现这个功能;剩余函数(…)也叫展开运算符
- 普通函数也能使用剩余运算符
- 数组 push pop unshift shift reverse sorp splice slice fliter jion map forREach indexOf lastIndexOf concat includes toString
- map 和 filter 的区别,、
- fliter 返回值是由回调函数执行返回 true 的那些项组成的新数组
- map 返回值是一个新数组,长度===arr 的长度;项是由回调函数的每一次执行的返回结果组成的
- 字符还串 replace split slice subString includes indexOf toUpperCase toLowerCase charAt charCodeAt,字符串的所有方法都不会改变原有的字符串
- 获取每个字符串的个数
var str = "smonsigjagidngoaenaoenbognaehuraehuabeiaerbiuaeerbngaegnagnkanoiawnoawnegoagnoianoiangoanegioniognaewonoagawhgineaonboaernb"; obj = {}; for (var i = 0; i < str.length; i++) { var key = str[i]; //判断数组中的属性名是否存在 if (typeof obj[key] === "undefined") { obj[key] = 1; //如果不存在就给属性值赋1 } else { obj[key] += 1; //如果存在就给属性值加一 } } var newMax = 0; var code = ''; //遍历对象查找字符出现最多的数 for (var k in obj){ //判断如果出现辞职大于newMax就将这一项付给newMax if(obj[k] > newMax){ newMax = obj[k]; code = k; } } console.log(`字符串str中最大的字符是${code}一共有${newMax}个`);
- 循环对象要用 for in 循环
- 对数组进行排序
var arr = [4, 3, 1, 7, 8, 5, 34, 56, 78, 98, 13]; function sory(arry) { if (arry.length <= 1) return arry; var lest = Math.floor(arry.length / 2); var left = []; var reight = []; var itrm = arry[lest]; for (var i = 0; i < arry.length; i++) { if (i != lest) { if (arry[i] < arry[lest]) { left.push(arry[i]) } else { reight.push(arry[i]); } } } return sory(left).concat(itrm, sory(reight)); } console.log(sory(arr));
回流和重绘
- 重绘(repaint):
当元素样式的改变不影响布局时,浏览器将使用重绘对元素进行更新,此时由于只需要 UI 层面的重新像素绘制,因此损耗较少。
常见的重绘操作有:- 改变元素颜色
- 改变元素背景色
- more ……
- 回流(reflow):
又叫重排(layout)。当元素的尺寸、结构或者触发某些属性时,浏览器会重新渲染页面,称为回流。此时,浏览器需要重新经过计算,计算后还需要重新页面布局,因此是较重的操作。
常见的回流操作有:- 页面初次渲染
- 浏览器窗口大小改变
- 元素尺寸/位置/内容发生改变
- 元素字体大小变化
- 添加或者删除可见的 DOM 元素
- ……
重点:回流必定会触发重绘,重绘不一定会触发回流。重绘的开销较小,回流的代价较高。
- 影响布局的就是回流(重拍)回流一定会引起重绘,不影响布局的就是重绘。
- 获取元素的集合
- [context].getElementsByClassName()获取 class 类名的集合,[context]是获取元素的范围
- getElementById()获取 id 类名的标签
- getElementsByTagName()获取元素标签名的集合
- querySelector()获取单个元素
- querySelectorAll()获取元素集合 它获取的时候可以使用 forEach 方法遍历集合
- document.head 获取 head 标签
- document.body 获取 body 标签
- 获取 html 标签,document.documentElement
- 获取 html(可视窗口)的高度:document.documentElement.clientHeight;
- 获取 html(可视窗口)的宽度:document.documentElement.chlientWidth
- 元素.getAttribute(‘xxx’) 获取元素的行内属性 xxx 对应的值
var divName = document.getElementsByClassName("box1")[1]; console.log(divName.getAttribute("name"))
2022 年 3 月 26 日
- 插入排序
let arry = [2, 3, 4, 53, 4, 5, 4, 32, 43, 54, 46, 578, 667]; function sort(arr = []) { for (let i = 0; i < arr.length; i++) { //索引i之前的那些项都是排好序的 let temp = arr[i]; let j = i - 1; while (j >= 0 && temp < arr[j]) { //判断arr[j]是否到了第一位以及判断temp和arr[j]的大小 arr[j + 1] = arr[j]; //对arr[j]进行位置调换 j--; } arr[j + 1] = temp; } return arr; } console.log(sort(arry));
4.DOM操作
-
nodeType:能够分辨节点到底是什么节点,只读
- 元素节点 ——1
- 属性节点 —— 2
- 文本节点 —— 3
- 注释节点 —— 8
- document —— 9
- DocumentFragment —— 11
获取行内属性节点:getAttributeNode()
-
节点类:
- arentNode 父节点
- childNodes 所有子节点的集合
- firstChild 第一个子节点
- lastChild 最后一个子节点
- previousSibling 上一个兄弟节点
- nextSibling 下一个兄弟节点
function previouSibing(ele) { let h2 = document.getElementsByTagName("h2")[0]; let per = h2.nextSibling; while (per && per.nodeType != 1) { per = per.nextSibling; } return per; } console.log(previouSibing());
-
元素类:
- children 所有子元素的集合
- firstElementChild 第一个子元素 IE9+
- lastElementChild 最后一个子元素 IE9+
- previousElementSibling 上一个兄弟元素 IE9+
- nextElementSibling 下一个兄弟元素 IE9+
-
…在函数形参中是剩余运算符,在其他地方时展开运算符
-
ele.appendChild(“需要插入的元素”):把创建的元素元素添加到 ele 内部的最后一项
如果添加一个已经存在的选中标签,那么不会增加,只是移动到子元素末尾
var ul = document.getElementsByTagName("ul")[0]; var div = document.createElement("div"); ul.appendChild(div); console.log(ul.innerHTML)
-
ele.insertBefore(div,a):给 ele 内部的 a 前面插入一个 div 标签
<div id="id1"> <p class="p1" id="p1">这是P1</p> </div> <script> var div = document.getElementById("id1"); var p1 = document.getElementById("p1"); var odiv = document.createElement("div"); var returnDom = div.insertBefore(odiv, p1); console.log(div); </script>
-
createElement(“div”);创建一个 div 标签
-
createTextNode(“创建一个文本节点”):创建一个文本节点
-
cloneNode:把某一个节点进行克隆
-
cloneNode(true):(深克隆)把节点包含的所有内容进行克隆
<div id="id1"> <p class="p1" id="p1">这是P1</p> </div> <script> var res = id1.cloneNode(); var res2 = id1.cloneNode(true); console.log(res); console.log(res2); </script>
-
removeChild:移除元素
<div id="id1"> 1111 <p class="p1" id="p1">这是P1</p> </div> <script> id1.removeChild(p1); console.log(id1); </script>
-
ele.getAttibule(“xxx”):获取行内属性
-
ele.removeAttibule(“xxx”):删除行内属性
-
ele.setAttribute(“xxx”,“xxx”):设置行内属性
box.setAttribute("index", 1); box.getAttribute("index"); box.removeAttribute("index"); console.log(box) // 设置 // box["aa"] = 22; // 获取 // box["aa"] //移除 // delete box["aa"];
-
获取日期 new Date();
- time.getFullYear():获取年
- time.getMonth():获取月
- time.getDate():获取日
- time.getHours():获取时
- time.getMinutes():获取分钟
- time.getSeconds():获取秒
- time.getDay():获取星期几
let time = new Date(); console.log(time); console.log(time.getFullYear()); console.log(time.getMonth()); console.log(time.getDate()); console.log(time.getHours()); console.log(time.getMinutes()); console.log(time.getSeconds()); console.log(time.getDay()); //Sat Mar 26 2022 16:14:02 GMT+0800 (中国标准时间) //2022 //2 //26 //16 //29 //55 //6
Date()括号中能够传入时间戳
-
time.getTime():是距离 1970 年 1 月 1 日的 00:00:00 的毫秒数
console.log(time.getTime(2022/12/3));
//1648283851978
- 后天给的时间戳算出日期:
new Date("时间戳");
就能转成公历日期 - 定时器:
setInterval(()=>{},1000)
//钟表函数 function times() { var apan = document.getElementsByTagName("span")[0]; let arry = ["日", "一", "二", "三", "四", "五", "六"]; setInterval(function () { let time = new Date(); let year = time.getFullYear(); let month = time.getMonth(); let date = time.getDate(); let hours = time.getHours(); let minutes = time.getMinutes(); let seconds = time.getSeconds(); let day = time.getDay(); apan.innerText = `现在是第${year}年${month + 1}月${date}日星期${arry[day]}${hours}时${minutes}分${seconds}秒`; }, 1000); } times();
2022 年 3 月 28 日
- 随机生成 4 位数字
function num(max, min) { return Math.floor(Math.random() * (max - min) + min); } function getCode() { var str = "0123456789"; var strin = ""; for (var i = 0; i < 4; i++) { strin += str[num(4, 0)]; } let div = document.getElementsByTagName("div")[0]; return div.innerHTML = strin; } getCode(); let timer = setInterval(() => { getCode(); }, 1000); setTimeout(() => { clearInterval(timer); }, 1000 * 10);
5.作用域
- 作用域:变量可作用的区域
- 全局作用域:在函数以外的区域
- 私有作用域:以函数为划分的区域
- 作用域链:函数会限制变量的作用域范围,而函数内是可以再嵌套函数的,函数的层层嵌套,就形成了一个作用域链。
- 当函数内使用某个变量的时候,会按照如下过程找到该变量:
- 先从自身所在作用域去查找,如果没有再从上级作用域当中去查找,直到找到全局作用域当中。
- 如果其中有找到,就不会再往上查找,直接使用。
- 如果都没有找到,那么就会报引用错误提示变量没有定义。
- 如果在函数内部声明的变量没有使用关键字就会被挂到 window 上。
- 变量提升:预解析、预编译
- var 只声明不定义
- function 既声明又定义
- 预编译:
- 生成 AO 对象;
- 把形参和变量声明的名作为 AO 对象的属性名,值统一;
- 实参值传到形参当中;
- 在函数体找函数声明,值赋予函数体;
- 全局预编译:
- 生成 Go 对象;
- 把形参和变量声明的名作为 AO 对象的属性名,值统一;
- 实参值传到形参当中;
- 在函数体找函数声明,值赋予函数体;
- return:下面的代码虽然不执行,但是变量会提升,下面的代码进行变量提升,return 后面的代码不进行变量提升
function fn(){ console.log(a); return function f1(){ } var a=3; } fn();
- 如果预编译进入 if 语句时 var 和 function(函数)都是只声明不提升
- 自执行函数在当前所在的作用域中不进行变量提升,自执行函数自己所形成的私有作用域照常进行
function f2(){ console.log("f2"); } // 自执行函数在此处不进行变量提升 (function (){ console.log(a);// undefined, 照常进行变量提升 var a=3; })();
2022 年 3 月 29 日
- 判断一个对象有没有该属性用 in
"d" in window//判断window中有没有d
; - let 和 const 这两个是没有变量提升的都是声明一个变量
const 声明的变量指针是不能改的
const num = 300; num = 200;
会报错 - const 声明变量必须赋值否则会报错
const num//会报错,没有初始值
- 在全局上下文中,基于 var 声明的变量是直接放在 GO(window)中,而基于 let 声明的变量是放在 VO(G)中 的,和 GO 没关系
- var 存在的变量提升,let 不存在变量提升
- var 允许重复声明,而 let 是不允许的[而且在词法分析阶段就过不去]
- let 会产生块级上下文,var 不会
- 关于暂时性死区问题:typeof 检测一个未声明的变量,不会报错,结果是 undefined 在 let 声明前使用就会报错
但是在加入了块级作用域的时候 let 和 const 之后,在其被声明之前对块中的 let 和 const 进行 typeof 操作会报错
-
- ECStack:execution context stack 执行环境栈
- heap:堆
- VO(G):variable object stack 执行环境栈
- GO:Global object 全局对象
- AO:Activation Object 执行上下文
-
console.log(a, b);//undefined undefined var a = 12, b = 12; function fn() { // undefined 12 console.log(a, b); var a = b = 13; console.log(a, b); // 13 13 } fn(); console.log(a, b); // 12 13
- 电脑显卡挂壁了,准备去修电脑,新电脑坑爹
闭包
-
全局栈内存页面关闭是会被释放,普通函数一般调用结束会被释放
-
闭包让你可以在一个内层函数中访问到其外层函数的作用域
-
闭包什么时候产生?
- 函数嵌套函数
- 被嵌套的函数内部引用了外部函数的形参或者变量
- 外部函数被调用
- 优点:
- 延长了局部变量的生命周期
- 可以通过闭包实现一些高级一点的功能
2022 年 3 月 30 日
- 用 setTimeout 实现 setInterval
function fn(){
console.log(666);
setTimeout(()=>{
fn()
},1000)
}
fn();
- 同步先执行,完成之后再执行异步
- 上级作用域:就是看这个函数是在哪个作用域创建的
- 全局变量:全局作用域下的变量;私有变量:私有作用域下的变量(形参,私有作用域下声明的变量)
- 预编译:函数形成一个私有作用域,形参赋值–>变量提升–>代码从上到下执行
- let 和 const 在声明之前都是不能使用的,如果使用会进入暂时性死区
- let 不能重复声明,能识别块级作用域,有暂时性死区
- const 声明的是一个常量,其他等同于 let
- this 执行主体:谁让这个函数执行。
- 全局下的 this 是 window
- 事件绑定高中对应的函数的 this 是绑定的那个函数
- 箭头函数中没有 this 和 arguments,他是把这两个关键字当做普通变量去对待。
- 函数执行,里面的 this 是谁就看执行前边有没有点,点前面是谁 this 就是谁,没点就是 window
- 自执行函数中的 this 就是 window
2022 年 4 月 1 日
- 作用域链:变量的查找机制,若查找到全局下都没有对应的变量,那么就会报错 xxx is not definef;
- 上级作用域:一个函数执行成的私有作用域的上级作用域是看这个函数在哪个作用域形成的
- 箭头函数将 this 和 arguments 当做普通变量去对待
- 普通函数:this 事件绑定对应的函数中的 this 是绑定的那个元素,全局下的 this 是 window,自执行函数里面的 this 是 window,定时器对应的回调函数中的 this 也是 window,其他看点,点前边是谁 this 就是谁,没有点就是 window
6.Git
git 常用命令。
创建 SSH Key
$ ssh-keygen -t rsa -C "youremail@example.com"
配置用户信息
$ git config --global user.name "Your Name"
$ git config --global user.email "email@example.com"
仓库
在当前目录新建一个 Git 代码库
$ git init
新建一个目录,将其初始化为 Git 代码库
$ git init [project-name]
下载一个项目和它的整个代码历史
$ git clone [url]
增加/删除文件
添加指定文件到暂存区
$ git add [file1] [file2] ...
添加指定目录到暂存区,包括子目录
$ git add [dir]
添加当前目录的所有文件到暂存区
$ git add .
添加每个变化前,都会要求确认
对于同一个文件的多处变化,可以实现分次提交
$ git add -p
删除工作区文件,并且将这次删除放入暂存区
$ git rm [file1] [file2] ...
停止追踪指定文件,但该文件会保留在工作区
$ git rm --cached [file]
改名文件,并且将这个改名放入暂存区
$ git mv [file-original] [file-renamed]
代码提交
提交暂存区到仓库区
$ git commit -m [message]
提交工作区自上次 commit 之后的变化,直接到仓库区
$ git commit -a
提交时显示所有 diff 信息
$ git commit -v
使用一次新的 commit,替代上一次提交
如果代码没有任何新变化,则用来改写上一次 commit 的提交信息
$ git commit --amend -m [message]
重做上一次 commit,并包括指定文件的新变化
$ git commit --amend [file1] [file2] ...
查看信息
显示有变更的文件
$ git status
显示当前分支的版本历史
$ git log
显示 commit 历史,以及每次 commit 发生变更的文件
$ git log --stat
搜索提交历史,根据关键词
$ git log -S [keyword]
显示某个 commit 之后的所有变动,每个 commit 占据一行
$ git log [tag] HEAD --pretty=format:%s
显示某个 commit 之后的所有变动,其"提交说明"必须符合搜索条件
$ git log [tag] HEAD --grep feature
显示某个文件的版本历史,包括文件改名
$ git log --follow [file]
显示指定文件相关的每一次 diff
$ git log -p [file]
显示过去 5 次提交
$ git log -5 --pretty --oneline
显示所有提交过的用户,按提交次数排序
$ git shortlog -sn
显示指定文件是什么人在什么时间修改过
$ git blame [file]
显示暂存区和工作区的差异
$ git diff
显示暂存区和上一个 commit 的差异
$ git diff --cached [file]
显示工作区与当前分支最新 commit 之间的差异
$ git diff HEAD
显示两次提交之间的差异
$ git diff [first-branch]...[second-branch]
显示今天你写了多少行代码
$ git diff --shortstat "@{0 day ago}"
显示某次提交的元数据和内容变化
$ git show [commit]
显示某次提交发生变化的文件
$ git show --name-only [commit]
显示某次提交时,某个文件的内容
$ git show [commit]:[filename]
显示当前分支的最近几次提交
$ git reflog
分支
列出所有本地分支
$ git branch
列出所有远程分支
$ git branch -r
列出所有本地分支和远程分支
$ git branch -a
新建一个分支,但依然停留在当前分支
$ git branch [branch-name]
新建一个分支,并切换到该分支
$ git checkout -b [branch]
新建一个分支,指向指定 commit
$ git branch [branch] [commit]
新建一个分支,与指定的远程分支建立追踪关系
$ git branch --track [branch] [remote-branch]
切换到指定分支,并更新工作区
$ git checkout [branch-name]
切换到上一个分支
$ git checkout -
建立追踪关系,在现有分支与指定的远程分支之间
$ git branch --set-upstream [branch] [remote-branch]
合并指定分支到当前分支
$ git merge [branch]
选择一个 commit,合并进当前分支
$ git cherry-pick [commit]
删除分支
$ git branch -d [branch-name]
删除远程分支
$ git push origin --delete [branch-name]
标签
列出所有 tag
$ git tag
新建一个 tag 在当前 commit
$ git tag [tag]
新建一个 tag 在指定 commit
$ git tag [tag] [commit]
删除本地 tag
$ git tag -d [tag]
删除远程 tag
$ git push origin :refs/tags/[tagName]
查看 tag 信息
$ git show [tag]
提交指定 tag
$ git push [remote] [tag]
提交所有 tag
$ git push [remote] --tags
新建一个分支,指向某个 tag
$ git checkout -b [branch] [tag]
远程同步
下载远程仓库的所有变动
$ git fetch [remote]
显示所有远程仓库
$ git remote -v
显示某个远程仓库的信息
$ git remote show [remote]
增加一个新的远程仓库,并命名
$ git remote add [shortname] [url]
取回远程仓库的变化,并与本地分支合并
$ git pull [remote] [branch]
允许不相关历史提交,并强制合并
$ git pull origin master --allow-unrelated-histories
上传本地指定分支到远程仓库
$ git push [remote] [branch]
强行推送当前分支到远程仓库,即使有冲突
$ git push [remote] --force
推送所有分支到远程仓库
$ git push [remote] --all
撤销
恢复暂存区的指定文件到工作区
$ git checkout [file]
恢复某个 commit 的指定文件到暂存区和工作区
$ git checkout [commit] [file]
恢复暂存区的所有文件到工作区
$ git checkout .
重置暂存区的指定文件,与上一次 commit 保持一致,但工作区不变
$ git reset [file]
重置暂存区与工作区,与上一次 commit 保持一致
$ git reset --hard
重置当前分支的指针为指定 commit,同时重置暂存区,但工作区不变
$ git reset [commit]
重置当前分支的 HEAD 为指定 commit,同时重置暂存区和工作区,与指定 commit 一致
$ git reset --hard [commit]
重置当前 HEAD 为指定 commit,但保持暂存区和工作区不变
$ git reset --keep [commit]
新建一个 commit,用来撤销指定 commit
后者的所有变化都将被前者抵消,并且应用到当前分支
$ git revert [commit]
暂时将未提交的变化移除,稍后再移入
$ git stash
$ git stash pop
忽略文件配置(.gitignore)
1、配置语法:
以斜杠“/”开头表示目录;
以星号“*”通配多个字符;
以问号“?”通配单个字符
以方括号“[]”包含单个字符的匹配列表;
以叹号“!”表示不忽略(跟踪)匹配到的文件或目录;
此外,git 对于 .ignore 配置文件是按行从上到下进行规则匹配的,意味着如果前面的规则匹配的范围更大,则后面的规则将不会生效;
2、示例:
(1)规则:fd1/*
说明:忽略目录 fd1 下的全部内容;注意,不管是根目录下的 /fd1/ 目录,还是某个子目录 /child/fd1/ 目录,都会被忽略;
(2)规则:/fd1/*
说明:忽略根目录下的 /fd1/ 目录的全部内容;
(3)规则:
/*
!.gitignore
!/fw/bin/
!/fw/sf/
说明:忽略全部内容,但是不忽略 .gitignore 文件、根目录下的 /fw/bin/ 和 /fw/sf/ 目录;
7.面向对象编程
- 单例模式:单例模式就是一个普通对象
- 高级单例模式:高级单例模式就是一个函数返回了一个对象;比普通高级在于可以有自己的私有变量
- 工厂模式:就是一个普通函数批量产生一些单例
- 构造函数:通过 new 执行一个函数的时候会先开辟一个堆内存,然后把函数中的 this 指向这个堆内存,执行过程跟普通函数一样,执行完成之后,若 return 后是个引用数据类型,则 new 执行的返回结果就是 return 后面的内容,否则就是这个堆内存
<script> function Person(name,age,sex){ this.name = name; this.age = age; this.sex = sex; } var person = new Person("小明",12,0); console.log(person);
- prototype 原型:原型里面存放的都是这个类的共有属性
- 类:就是在 js 中的一些函数,只不过是通过 new 去执行
- 实例:就是通过 new 执行哪个函数得到的结果
- 原型是一个函数的固有属性,只有当使用 new 去执行这个函数的时候原型才能体现他的这个原型中的属性,是当前函数的所有实例的公用属性
- 所有函数的默认原型(固有属性)都会有一个属性 constructor 属性就是函数本身
- 所有的实例都会有一个属性__proto__;属性值是当前实例所属类的 prototype(原型)
- 实例去调用某些属性的时候,先去看看自己有没有这个私有属性,有的话就是用自己私有的,没有的话就去所属的原型上查找;(通过__proto__)找所属类的原型)
- 类的默认原型是 Object 类(基类)的实例,类的默认原型的__proto__是 Object 的 prototype
- 原型链是属性的查找机制:先在自己身上找这个属性,没有的话通过__proto__往所属类的原型上找,这个原型上没有的话,就接着通过这个原型的__proto__当前原型所属类的原型上去找,一直找到基类的原型。
2022 年 4 月 2 日
- 类的原型上存放的属性,都是提供 Student 的实例去使用的公用属性
- new 的执行过程:先开作用域,开了一个堆内存,让函数中的 this 指向这个堆,然后形参赋值变量提升,然后代码从上到下执行
- return 的问题:return 后边跟一个引用类型则返回的就是写的这个引用类型,否则返回时这个堆内存(this–>实例)
- this 专有名词叫执行主体
- 严格模式"use strict"下 this 不指定就是 undefined
- 全局作用域下 var 或 function 出来的变量会同时给 window 这个对象增加对应的属性
- 函数都用 prototype 里面用来存储实力能够使用的共有属性,自带的(默认)的 prototype 上都有 constructor:指的是这个构造函数本身,所有的实例都有一个__proto__指的是所有属性类的 prototype
- 实例.xxx:先在自己身上查找这个属性,没有的话,通过__prot__向上层对象 A 查找,上层对象 A 若也没有这个属性,则接着通过__proto__向 A 的上层对象 B 中查找。一直找到基类(Object 类)de 原型(prototype),这时都没有对应的属性时,结果就是 undefined,因为基类的 prototype 的__proto__是 null,也就是说基类的 prototype 是最顶层
- constructor 这个属性之所以能用来判断数据类型,是因为实例调用了 constructor 都是调用的原型上的 constructor,指向就是构造函数本身
- A instanceof B:从 A 到基类的原型,这条原型链上有没有 B 的原型存在;
特殊:只能用于引用数据类型,值类型不适用;
- 使用 hasOwnproperty 可以判断属性是否是私有的:返回值是 true 就是私有的,是 false 就是公有的
p1.hasOwnproperty(k)
//封装一个函数判断元素是否在公有属性上与hasOwnProperty作用相同 function P1(name, age) { this.name = name; this.age = age; } let p1 = new P1("one", 24); P1.prototype.tos = "12"; Object.prototype.hasPubProperty = function (key) { return (key in this) && !this.hasOwnProperty(key); }
2022 年 4 月 4 日
- 所有的 this.xxx 的属性都是当前实例的私有属性
- 一般我们会把方法类的属性放到原型上
- 约定的规范:原型上的方法中的 this 保证是当前类的实例
- new 的执行过程:先开辟一个作用域,然后开辟一个堆内存,this 指向这个堆,形参赋值变量提升,代码执行。
new 返回值:不是引用的话就是默认返回 this
- call 执行函数并且改变函数 this 指向
f.call(xxx,a,b,c)//a,b,c是传给f的实参
,call 中的第一个参数是用来修改函数中的 this 指向,call 从第二个参数开始就是传给函数的实参 - 实现构造函数 new 方法
function Person(name, age, sex) { this.name = name; this.age = age; this.sex = sex; } function myName(C, ...ages) { //new的特性是开辟一个新堆空间 let obj = {}; //让实例指向他的构造函数 obj.__proto__ = C.prototype; //改变this指向指向他开辟的空间obj let a = C.call(obj, ...ages); //判断构造函数执行结构,如果是引用类型就返回它执行结构,如果是值类型就返回this return typeof a == "object" ? a : obj; } let p1 = new Person("小康", 12, 1); let p2 = myName(Person, "小康", 12, 1)
- 手写一个 instanceof 方法
var ary = []; function myInstance(a, b) { //判断a.prototype是否存在并且a.__proto__ !== b.prototype while (a.__proto__&&a.__proto__ !== b.prototype) { a = a.__proto__; if (a.__proto__ === null) { return console.log("没有") } } return true; } console.log(myInstance(ary, Array)); console.log(myInstance(ary, Object));
- 运算符优先级(从高到底)
2022 年 4 月 5 日
- 将类数组转化为数组的方法
- 用…展开符
[...arguments]
- 结合 call 来实现:
[].slice.call(div1)
先通过数组找到 slice 方法,通过 call 让这个 slice 执行,并且把其中的 this 变成类数组 - 创建一个新数组,把新数组中的内容循环放在新的数组中
- Array.from(xxx)返回一个数组项目跟 xxx 一样
- 用…展开符
- Object.keys(obj)用来循环对象,返回值是将对象属性名放在一个数组中
- 捕捉报错的方法
try{要执行可能会报错的代码}catch(形参){catch小括号中的形参对应的是上面运行错误信息;上面运行出错,就会走到这个catch,但是不会影响主体代码执行}
执行 try 中的代码如果报错就执行 catch 大括号中的代码 - this 指向问题
- 全局下的 this 是 window
- 自执行函数 this 是 window
- 事件绑定对应函数中的 this 是绑定的元素
- 其他看点,点前面是谁 this 就是谁,没有点就是 window
- new 执行类的时候,其中 this 是实例
- 类原型上的方法中的 this 是实例
- call 可以改变函数中 this 指向 f.call(111)
- apply bind 都是改变函数中 this 指向的
- 箭头函数中没有 this,他当中使用的 this 是上级作用域的 this
- apply:apply 的功能和 call 一样;区别在于 call 执行的时候给函数传参是散开写的,apply 执行的时候给函数传参是一个集合的方式
- bind 和 call 的用法相同但是使用 bind 函数不会立即执行,而是会返回一个新函数,新函数执行的时候会让 fn 执行并且把 fn 中的 this 换成指定的 this,后面的参数传给 fn 的实参,新函数执行的时候会给新函数传参,会拼接到 bind 传入实参的后面
let res1 = fn.bind(obj,1,2,3,4,5)
; - call 不能改变 this 的情况:1、箭头函数;2、bind 的返回值
- Object.create 执行会返回一个空对象,这个对象的protp是指向 create 的参数
var obj = Object.create(Array);//obj的__proto__指向Array
- class 是 ES6 新增的一个用来创造类的方式,创造出来的类不能当做普通函数执行;
class 创造原型使用 eat(){},class 中没法添加值类型
class Person { //创造一个名为Person的类 constructor(name, age, sex) { //constructor是class规定的属性 console.log(arguments); //设置私有属性 this.name = name; this.age = age; this.sex = sex; } //私有属性height height = 1; //建立名为eat的原型,形参food eat(food){ console.log(`${food}`) } //static声明的是静态属性,指的是Person自己的属性 static qqq = 888; static ttt = 999; static yyy = 666; } let p1 = new Person("小孔", 12, 0); console.log(Person.qqq);//输出Person内置的静态属性qqq,它的实例是不能使用静态属性
- 函数的三种角色:普通函数、对象、类
2022 年 4 月 6 日
- Array.isArray(xxx):判断 xxx 是不是一个数组
- Array.from(xxx):把类数组 xxx 转成一个数组,返回值就是这个数组
- Object.keys(xxx):把 xxx 这个对象的中的所有私有属性名拿出来组成一个数组返回
//将obj私有属性都拼接起来
Object.prototype.qq = 123;
var obj = {
name: "大哥",
age: 13
}
function obje(one) {
var h1 = document.getElementById("h1");
var a = Object.keys(one);
var str = "";
a.forEach(itm => {
str += `${itm}是${one[itm]};`;
});
h1.innerText = str;
}
obje(obj)
-
对页面插入并且排序
var list = [{ name: "小红", age: 10 }, { name: "小红2", age: 120 }, { name: "小红3", age: 140 }, { name: "小红4", age: 105 }, { name: "小红5", age: 106 } ]; var ul = document.getElementsByTagName("ul")[0]; var but = document.getElementsByTagName("button"); class Proson { constructor(ul, but) { this.ul = ul; this.but = but; } butt(array) { this.one(array); this.but[0].onclick = () => { this.one(this.srop(array)); } this.but[1].onclick = () => { this.one(this.srop1(array)); } } one(array) { var str = ""; array.forEach(ele => { str += `<li>姓名是${ele.name};年龄是${ele.age};</li>` }); this.ul.innerHTML = str; } srop(array) { return array.sort((a, b) => { return a.age - b.age; }) } srop1(array) { return array.sort((a, b) => { return b.age - a.age; }) } } var but = new Proson(ul, but); but.butt(list)
2022 年 4 月 8 日
-
js 中的数据类型
-
原始值类型(基本数据类型&值类型 0)
- number 数字
- string 字符串
- boolean 布尔
- unll 空
- undefined 未定义
- symbol 唯一值(ES6+)
- bigint 大数(ES6+)
-
对象类型(引用数据类型)
- 标准普通对象 例如:{x:10,y:20}
- 标准特殊对象
- new Array 数字
- new Regexp 正则
- Math 数字函数对象
- new Data 日期对象
- new Error 错误对象
- set/Mop (ES6+新增的数据结构)
- …
-
非标准特殊对象 例如:new Number(1) -> 原始值类型对应的"对象类型"实例
-
函数对象 function
-
-
创造一个数字
- 字面量(创建的原始值) var num1 = 1;
- 构造函数(创造出来的是非标准特殊对象)var num2 = new Number(10);
num2 是 Number 类的一个实例,可以直接调用 Number.prototype 上的方法,从严格意义上讲,num1 不是 Number 类的实例(因为实例都是对象类型的),按理 num1 应该是无法调用 Number.prototype 上方法的,但是实际操作中,是可以调用的,所以也可以称 num1 是 Number 类的实例;
- num1 Number(null)默认会把原始值转换为对象,然后再去调用 toFixed 方法,我们把这种操作(“把原始值准换问实例对象”)称之为“装箱”!!
- num2+10 ->20 游览器会默认把实例对象转换为对应的原始值,然后再进行数学运算,这个过程称之为“拆箱”
-
- 装箱:new Number(num)或者 Object(num)
- 拆箱:把对象转化为原始值
- 首先调用对象的"Symbol.toPrimitive"函数
- 如果对象不具备这个属性,则调用“对象.valueof()”函数,看获取的是不是原始值,是原始值则实现目标
- 如果不是原始值,则继续调用"对象.string()"函数,将其转换为字符串!
- 如果最后的目标变为数组,则再把字符串转换为数组
-
parseInt/parseFloat 都是用来把其他值转换为数字:从字符串(传递的不是字符串,也要先转换为字符串)左侧第一个字符串开始查找,把找到的有效数字字符转换为数字,遇到一个非有效的,则停止朝招!一个都找不到,结构就是 NaN!
-
i++ / i-- / ++i / +i 一定是数学运算;i = i + 1; 可能是字符串拼接
-
+val 就是将 val 转化为数字
-
-
函数的创建
- 开辟堆内存空间(16 进制地址)
- 存储三部分内容
- 声明其作用域(在哪一个上下文中创建的那么其作用域就是谁)
- 和普通队形对样,存储键值对
- name 函数名
- length 形参个数
- prototype
- __proto__
- 把空间地址赋给函数名(变量名)
-
函数执行:
- 开辟一个全新的私有执行上下文并进栈执行
- EC(?)
- AO(?)私有变量对象:存储当前上下文声明的私有变量的
- 代码执行之前处理的事情
- 初始化作用域链<函数私有上下文,函数作用域>
- 初始化 THIS 指向
- 初始化 ARGUMENTS
- 形参赋值[形参是私有变量]
- 变量提升
- 代码执行
- 私有上下文的释放问题
2022 年 4 月 9 日
- ES6 新语法规范:解构赋值
- 对数组进行解构
- 对对象进行解构
让“=”左侧出现和右侧值相类似的结构,然后快速取出某一部分的值,赋值给对应的变量
let arr = [1,2,3,4];
let [a,b] = arr;
console.log(a,b);//取出第一项和第二项1,2
let [,a,b] = arr;
console.log(a,b);//取出第二项和第三项2,3
let [a,...b] = arr;
console.log(a,b);//取出第一项付给a,剩余全部付给b,a = 1, b =[2,3,4]
let arr1 = [10];
let [a,b = 0] = arr1;
console.log(a,b); //给b赋默认值
- 微软开发的浏览器:
- IE 6~11 内核:trident
- ES3 兼容 IE 的比较多
- ES5 的东西不兼容 IE6~8
- ES6+不兼容 IE
- Edge 非 IE 内核,它是 Chromium 内核「和谷歌的 webkit 内核很相似」
谷歌浏览器:blink(webkit分支)
火狐:Gecko
.....
==============
真实项目开发,我们使用ES6语法,如果需要兼容 IE(>=IE10),我们需要基于 babel 模块,把ES6的语法转换为ES5....
https://babeljs.io/
- 把类数组转化为数组 Array.from();
- JS 中有两种编程方式:函数式编程、命令式编程
- @函数式编程:把需要实现的功能封装成方法(内置 API/自己封装的 API),直接调用方法去处理,无需我们关注 How(如何实现),而需关注 What(结果)即可!
arr.forEach((item,index)={})
- 优先推荐使用函数式编程
- 好处:开发效率高、方便维护、低耦合高内聚…
- 弊端:无法灵活掌控循环过程(无法终止循环或者间隔 N 项循环)
- @命令式编程:关注程序的具体实现(How)
for(let i = 0 ; i < arr.length;i++){}
- 好处:可以灵活把控每一步骤,项目中需要灵活管控的需求,都使用命令式编程
- 弊端:开发效率低、代码冗余度高…
- @函数式编程:把需要实现的功能封装成方法(内置 API/自己封装的 API),直接调用方法去处理,无需我们关注 How(如何实现),而需关注 What(结果)即可!
- JSON 只是一种数据格式,不是新的数据类型:JSON 格式的对象,JSON 格式的字符串
- 属性名用"双引号"
2022 年 4 月 11 日
- 函数中的 this 是谁有个前提是函数的某一次执行
- 使用 call 更改 this 指向的继承是私有属性的继承
- JSON.stringify 将对象转为 json 格式 , JSON.parse 将 json 格式文件转为数组
8.正则
2022 年 4 月 12 日
- 正则是一种专门用来处理字符串的规则
- 处理:匹配(test)、捕获
- 有一段正则和一个字符串:匹配的意思是看看这个字符串是否符合这个正则定义的规则
- 有一段正则和一个字符串:捕获的意思就是把这个字符串符合这个规则的部分获取到
var reg = /1/ //字面量 /元字符/ 定义一个规则:字符串中得有1
var reg2 = new RegExp('1') //构造函数
reg.test('aadfawaewaewfe')
-
字符串中的转义,就是把字符串中有特殊含义的字符转成字符串本身(不代表任何含义)
-
特殊的元字符:
- \d 代表数字:
var reg = /d/ //定义一个规则:字符串中得有d
- \D 代表除了数字以外的其他字符
- \w 代表数字或字母或下划线
- \W 代表数字字母下划线以外的其他字符
- ^ 代表以什么开头,若写就得在正则的最前面
var reg1 = /^1/
- $ 代表以什么结尾,若写就得在正则的最后面
var reg2 = /1$/
var reg11 = /21$/
规则是:字符串必须是 1 结尾而且还是得"这个"1 前面必须是 2
- [^xy$] 要不就是以 x 开头结束,要不就是以 y 开头结尾
- [^ab ] 除了 ab 以外的其他字符
- [a-z] 大白话就是小写字母
- a|b //a 或 b
- . 代表除了换行以外的其他字符
在[]中的.代表.本身
- () 提升优先级,分组(捕获时用到的感念)
/^(11|12)$/ //要不11要不12
- 量词元字符(量:数量)要修饰的字符的后面
- ?/\d?/ 代表前面的字符出现 0 次或 1 次
- + 代表前边的字符出现 1 到多次
- * 代表前面的字符出现 0 到多次
- {n} 代表前面的字符出现 n 次
- {n,} 前面的字符出现 n 到多次
- {n,m} 代表骑前面的字符出现 n 到 m 次
- 修饰符
- g global 全局匹配,最多用于捕获,匹配时很少用到
- i ignoreCase 忽略大小写
- m multiline 多行匹配
- \d 代表数字:
-
正则的捕获:exec
- 捕获的贪婪性:一次性获取尽可能多的去获取内容,想解决这个贪就在量词后面加一个?
- 捕获的懒惰行:只获取一次;g 用来解决这个懒,使用修饰符 g
2022 年 4 月 13 日
- 捕获的方法.match:对批量捕获有好处
- 在正则没有 g 修饰符的时候,match 捕获的结构和 exec 捕获结构是一样的,一但有 g 的时候,match 可以将目标字符串中所有匹配大正则的部分都捕获到,但是不能捕获小分组
- 字符串的 replace 方法
str.replace(reg, function (a, b, c) {
//这个回调函数什么时候执行每当字符串中有匹配正则的时候,这个回调就会执行一次
//字符创中有多少匹配正则的部分这个回调函数就会执行多少次
//这个回调函数的参数第一项指的是大正则捕获的内容,后边都是小分组捕获的内容
obj[b] = c;
})
- 字符串.trim()是一个用来去除字符串首位空格的方法
9.js 盒模型
![E6995ZB%5~YT1}D89YAUTP](https://cdn.jsdelivr.net/gh/yangyongit/image/imageE6995ZB%5~YT1}D8
9YAUTP.png) 4. js
js:
offset类
offsetTop offsetLeft offsetWidth offsetHeight offsetParent
--offsetWidth:width+左右padding+左右border
client类
clientTop clientLeft clientWidth clientHeight
--clientWidth:width+左右padding
scroll类
scrollTop scrollLeft scrollWidth scrollHeight
--scrollWidth:若内容不溢出等同于clientWidth
一旦溢出了 左padding+内容的实际宽度
2022 年 4 月 15 日
- 设置元素的样式:
- box.style.xxx = xxx 写在元素的行内样式上
- box.style.cssText = “width : 100px ; height : 100px;”
- box.cassName = xxx 给元素设置样式类名,从而让其拥有摸个样式
- box.classList.add(xxx) 增加样式类名
- box.classList.remove/replace/contains/toggle…
- 获取元素的样式
console.log(box.style.xxx
获取写在元素"行内"上的样式[特点:样式只有写在行内上才能获取]
-window.getComputedStyle([element],[伪类 after/before ])返回的是包含当前元素的所有"经过游览器计算后"的样式对象[但凡元素经过游览器渲染,所有样式都是被计算的;所以不论写在行内还是样式表中,或者写或者不写,样式都可以获取到]- ele.getBoundingClientRect() 返回一个对象,包含当前元素和可视窗口相关的位置信息
- left/right 盒子左边框/有边框距离可视窗口’左边’的距离
- top/bottom 盒子上边框/下边框距离可视窗口’上边’的距离
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HUwACOQE-1650296070122)(https://cdn.jsdelivr.net/gh/yangyongit/image/image4b50cf1929e6a2b613a7cc7a7939464.png)]
- JS 盒子模型属性:获取元素的相关样式
- clientWidth/clientHeight:获取元素可视区域的宽高[真实内容+padding],不受溢出的影响
- clientTop/clientLeft:获取元素上边框和左边框的高度
- offsetWidth/offsetHeight:clientWindth/clientHeight 基础上’加上’左右/上下边框
- offsetTop/offsetLeft:获取当前元素距离其父级参照物的距离「上边距/左边距 从元素的外边框–>父参照物的内边框
- offsetParent:获取元素的父级参照物[同一个平面(文档流)中,最外层元素是内层所有元素的父级参照物]
- 默认情况下,元素的父级参照物都是 body;
body.offsetParent === null
- 想要修改父级参照物,可以修改元素的’定位规则’[目的:让其脱离文档流]
- 默认情况下,元素的父级参照物都是 body;
- scrollWidth/scrollHeight:获取元素距离其父级元素参照物的距离[上边距/左边距 元素的外边框->父级参照物的内边框]
- 在没有内容溢出的情况下:获取的值和 clientWidth/clientHeight 相同
- 在有内容溢出的情况下:获取的是真实宽高(包含溢出的内容)获取是约等于的值[因为根据设置 overflow 的值不同,获取的结果也不尽相同,而且不同游览器下获取的值也不尽相同]
- scrollTop/scrollLeft:获取盒子(或者页面)卷去的高度和宽度【这两个属性是 13 个属性中唯一’可读写’的,其余 11 个属性都是可读的】
- 总结
- 只有 scrollTop/Left 是"可读写的",其余属性都是"只读"的
- clientWidth/Height、clientTop/Left、offsetWidth/Height、scrollWidth/Height 都是获取元素本身的样式(例如:宽高和边框等);而 offsetTop/Left/Parent 获取的都是元素距离被人的距离;
- 操作游览器的盒模型都是基于 document.documentElement(HTML 元素)进行操作的;
- 获取到的样式值都是经过四舍五入的整数值,不会出现小数!!
- 图片延迟加载(懒加载):
- 最开始加载页面的时候,IMG 的 SRC 不赋值(这样就不会加载真实的图片,把真实的图片的地址付给 IMG 的自定义属性,方便后期想要加载真实图片时候获取)
- 如果 SRC 不赋值或者加载图片是错误的,会显示"碎图",这样样式不美观,所以:我们最开始让 IMG 是影藏的[可以设置 display,也可以设置透明度为(透明度改变可以设置过渡效果)]
- 给图片所在的盒子设置背景占位图(或者背景颜色),在真实图片没有加载之前,用其占位符[盒子宽高实现设置好的]
- 啥时候加载
- 当页面第一次渲染完成(其它资源加载完成,例如:window.onload)
- 把出现在当前可视窗口内的图片进行加载
- 如何加载 - 获取图片的自定义属性值,拿到真实的图片地址 - 给图片的 SRV 赋值真实地址:如果图片可以正常加载成功,则让 IMG 显示
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6TEFfI6L-1650296070123)(https://cdn.jsdelivr.net/gh/yangyongit/image/image6cf926b95e7204e859c78b9981380f1.png)]
- getBoundingClientRect()用于获得页面中某个元素的左,上,右和下分别相对浏览器视窗的位置。
10.游览器底层渲染机制
2022 年 4 月 16 日
- IntersectionObserver:ES6 新增的一个内置类
- 不兼容 IE 游览器
- new IntersectionObserver(callback’回调函数’) 创建它的一个实例:创建一个监听器,用来监听一个或者多个 DOM 元素和游览器可视窗口的交叉状态和信息
- 回调函数执行:
- 创建监听器、且监听了 DOM 元素会立即执行一次(连续监听多个 DOM 只触发一次,但是如果监听是分隔开的,没新监听一个元素都会触发执行一次)
- 当监听的元素和可视窗口交叉状态改变,也会触发执行【默认是"一露头"或者"完全出去",会触发;当然可以计入第二个 Options 配置项中的 threshold 来指定规则:】
- threshold:[0] 一露头&完完全出去
- …
- threshold:[1] 完全出现&出去一点
- 回调函数有一个参数 changes:是一个数组,记录了每一个监听元素和可视窗口的交叉信息
- boundingClientRect:记录当前监听元素的 getBoundingClientRect 获取的值
- isIntersecting:true/false true 代表出现在可视窗口中,false 则反之
- target:储存当前监听的 DOM 元素对象
- 当交叉状态[出现在可视窗口、离开可视窗口]发生改变,都会触发监听器的回调函数执行
- 在回调函数中可以获取所有监听的 DOM 和可视窗口的交叉信息
- 回调函数执行:
- 监听某个项目用 observe,移除监听 unobserve
/*
IntersectionObserver:ES6新增的一个内置类
+ 不兼容IE浏览器
+ new IntersectionObserver(callback) 创建它的一个实例:创建一个监听器,用来监听一个或者多个DOM元素和浏览器可视窗口的交叉状态和信息
+ 当交叉状态「出现在可视窗口中、离开可视窗口」发生改变,都会触发监听器的回调函数callback执行
+ 在回调函数中可以获取所有监听的DOM和可视窗口的交叉信息
*/
let box1 = document.querySelector('#box1'),
box2 = document.querySelector('#box2');
// 创建监听器
let ob = new IntersectionObserver((changes) => {
/*
回调函数执行:
+ 创建监听器、且监听了DOM元素会立即执行一次(连续监听多个DOM只触发一次,但是如果监听是分隔开的,每新监听一个元素都会触发执行一次)
+ 当监听的元素和可视窗口交叉状态改变,也会触发执行「默认是“一露头”或者“完全出去”,会触发;当然可以基于第二个Options配置项中的threshold来指定规则;」
+ threshold: [0] 一露头&完全出去
+ ...
+ threshold: [1] 完全出现&出去一点
----
changes:是一个数组,记录了每一个监听元素和可视窗口的交叉信息
+ boundingClientRect:记录当前监听元素的getBoundingClientRect获取的值
+ isIntersecting:true/false true代表出现在可视窗口中,false则反之
+ target:存储当前监听的这个DOM元素对象
+ ...
*/
console.log(changes);
}, { threshold: [1] });
// 监听某个DOM元素和可视窗口的交叉状态改变;unobserve移除监听;
ob.observe(box1);
ob.observe(box2);
-
游览器底层渲染机制:当我们从服务器获取代码后,浏览器是如何把代码渲染为页面及相关效果的
-
CRP(关键渲染路径)性能优化法则:了解浏览器底层处理的具体步骤,针对每一个步骤进行优化
-
JS 种的同步和异步编程
- 同步编程:上一件事情没有处理完,下一件事情无法处理
- 异步编程:上一件事情即便没有处理完,也无需等待,可以继续处理后面的事情
-
进程和线程:一个进程中可能包含多个线程
- 进程:一般代表一个程序(或者游览器打开一个页面就开辟一个进程)
- 线程:程序中具体干事的人
-
游览器是多线程,当基于游览器打开一个页面(开辟一个进程),会有不同的线程同时去做多件事情
- GUI 渲染线程:用来渲染和解析 HTML/CSS 的以及绘制页面
- JS 引擎线程:用来渲染和解析 JS 的
- HTTP 网络线程:用来从服务器回去相关资源文件[同源下,最多开辟 5~7 个 HTTP 线程]
- 定时器监听线程:监听定时器是否到时间的(计时的)
- 事件监听线程:监听时间是否触发的
- …
-
游览器底层渲染机制
- 步骤一:生成 DOM 树[DOM TREE]
- 当我们从服务器获取 HTML 代码后,游览器会分配’GUI 渲染线程’自上而下解析代码
- 遇到<link>:分配一个新的’HTTP 线程’去获取对应的 CSS 资源,GUI 继续向下渲染[异步]
- 遇到<style>:无需获取资源,但是 GUI 也不会去立即渲染 CSS 代码,防止渲染顺序错乱;会等到 DOM 结构渲染完成,访问的 link 等资源也获取到了,按照之前的书写的顺序,依次渲染样式!
- 遇到@import:也需要服务器回去资源(基于 HTTP 线程),但是这个操作会把’GUI 线程’挂起,无法继续向下渲染,直到 CSS 资源获取之后,GUI 才会继续向下渲染[同步:阻碍 GUI 渲染]
- 遇到<img>:和 link 是一样的,也是异步操作,也会分配新的 HTTP 线程去获取图片资源,GUI 继续向下渲染
- 遇到<script>:因为 JS 中要设计 DOM 操作,所以遇到<script>,默认会阻碍 GUI 的继续渲染;
- 先分配 HTTP 线程去获取 JS 资源;
- 资源获取后在分配 JS 引擎线把 JS 代码线渲染;
- 都渲染完了,GUI 在继续向下渲染;
- 自上而下处理完后,莫钱只是吧页面中的 DOM 结构(节点),构建出对应的层级关系!而这就是 DOM 树![触发 DOMContentLoaded 事件]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8ZhfyDbV-1650296070123)(https://cdn.jsdelivr.net/gh/yangyongit/image/image44aca8883347bc9340bc02d38651b7d.png)]
- 当我们从服务器获取 HTML 代码后,游览器会分配’GUI 渲染线程’自上而下解析代码
- 步骤二:生成 CSSOM 树[CSSOM TREE]
- DOM 树生成后,等待 CSS 资源都获取到,依次按照 CSS 书写的顺序,依次渲染和解析 CSS 代码(GUI 渲染线程),生成 CSSOM 树:计算出每个节点具备的样式[含某些样式是继承过来的等]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ID9JAVvN-1650296070124)(https://cdn.jsdelivr.net/gh/yangyongit/image/imaged82a74afef09363e06053bc310254eb.png)]
- DOM 树生成后,等待 CSS 资源都获取到,依次按照 CSS 书写的顺序,依次渲染和解析 CSS 代码(GUI 渲染线程),生成 CSSOM 树:计算出每个节点具备的样式[含某些样式是继承过来的等]
- 步骤三:合成渲染树[RENDER TREE]
- 把 DOM 树和 CSSOM 树合并在一起,生成渲染树!
- 步骤四:Layout 布局 & 回流重排
- 按照当前可视窗口的大小,计算每一个节点在视图中的位置和大小
- 步骤五:分层
- 计算每一层(每一个文档流)中各个节点的具体绘制规则
- 步骤六:Painting 绘制 & 重绘
- 按照计算好的规则,一层层的进行绘制
- 步骤一:生成 DOM 树[DOM TREE]
-
CRP 优化技巧:
- 我们最好把所有的资源合并压缩为一个,只请求一次就把所有样式获取到即可;分多次请求,因为 HTTP 的并发限制和可能出现的网络拥堵等问题,导致并不如请求一次快!
- CSS 合并为一个
- JS 合并为一个
- 雪碧图
- 尽可能不要使用@import 导入式,因为它会阻碍 GUI 的渲染;如果 CSS 样式代码不是很多,使用 style 内嵌式更好(尤其是移动端开发);但是如果代码很多,还是使用 link 外链式(但是最好把 link 放在<head>中);
- 我们最好把所有的资源合并压缩为一个,只请求一次就把所有样式获取到即可;分多次请求,因为 HTTP 的并发限制和可能出现的网络拥堵等问题,导致并不如请求一次快!
-
图片懒加载一定要处理:不要在第一次渲染页面的时候,让图片资源的请求去占用有有限的 HTTP 线程以及宽带资源,优先本着 CSS/JS 资源获取;当页面渲染完成后,再去根据图片是否出现在视口中,加载真实图片;
-
关于<script>的优化
- 最好把<script>放在 BODY 的末尾,等待 DOM 结构的加载完成,再去回去和解析 JS[此时就可以获取 DOM 元素了]
- 也可以基于事件监听去处理
- window.onload:等待页面汇总所有资源(含 DOM 结构/CSS/JS 等资源)都加载完触发
- window.addEventListener(‘DOMContentLoaded’,funtionz(){}):只需要等待 DOM 结构加载完就触发,所以触发时机比 window.onload 会早很多
- 也可以给<script>设置 async 或者 defer 异步属性 - async[获取异步,渲染同步]:遇到<script async>,分配新的 HTTP 去获取资源,GUI 会继续渲染。当资获取后,立即结束 GUI 渲染,让 JS 引擎线程去渲染解析 JS;JS 代码渲染完,再去执行 GUI 渲染! - defer[获取异步,渲染异步]:遇到<script defer>分配 HTTP 去获取资源,此时 GUI 继续渲染;当 DOW 结构渲染完成,而且设置 defer 的 JS 资源也都获取到了,按照之前编写的 JS 顺序,依次渲染 JS 代码!
- async 的特点是:只要 js 代码获取到,就会立即执行,不管书写的先后顺序,使用与 JS 之间不存在依赖的时候"谁先请求回来先执行谁"
- defer 的特点是:必须当代 GUI 以及所有设置 defer 的 JS 代码都获取到,在按照之前书写的顺序,依次渲染和解析,即实现了资源的异步获取,也可以保证 JS 代码之间的依赖关系!
-
加快 DOM TREE 的构建
- 减少 HTML 的层级嵌套
- 使用符合 W3C 规范的语义化标签
- …
-
加快 CSSOM TREE 的构建
- 选择器层级嵌套不要过深(或者前缀不要过长)[选择器的渲染顺序:从右到左]
- 减少 CSS 表达式的使用
- …
-
操作 DOM 比较消耗性能:大部分性能都消耗在了"DPM 的重拍(回流)和重绘"
页面第一次渲染,必然会出现一次 Layout(回流)和 painting(重绘);第一次渲染完成之后
- 重排(回流):如果浏览器的视口大小发生改变或者页面中"元素的位置、大小"发生改变再或者"DOM 结构"发生变化(删除、新增元素或者挪动位置)…游览器都需要重新计算节点在视口中(本层)的最新位置[也就是重新 Layout],完成后在分层和重新绘制—>此操作非常消耗性能,所以我们应该尽可能减少重排(回流)的次数
- 重绘:视口\元素的位置大小都不变,只是修改了一些基础样式(例如:背景颜色、文字颜色、透明度…),此时我们无需重新 Layout,只需要重新 Painting 即可!—>重绘操作是比不可免的,只要想让页面第一次渲染完成还可以再改变,必然需要重绘;而且触发一次回流,也必然会经历重绘!
-
如果基于 JS 操作 DOM,那么前端性能优化必做的事情:减少 DOM 的重排(回流)
- 基于 Vue/React/Angular 等框架进行开发,我们是基于"数据驱动视图渲染",规避了直接操作 DOM,我们只需要操作数据,框架内部帮助我们操作 DOM(他们做了很多减少 DOM 重排的操作)
- 读写分离
- 新版本游览器中存在"渲染队列机制":当前上下文代码执行过程中,遇到修改元素样式的操作,并不会立即去修改样式,而是把其挪至到渲染队列中,代码继续向下执行…当代码执行完成后,此时会把渲染队列中所有修改样式的操作统一执行一次[触发一次重排]
- 但是在此过程中如果遇到了获取元素样式的操作,则"刷新渲染队列"(也就是把目前队列中的操作执行一次),引发一次重排!
把获取演示的操作和修改样式的操作分离开
- 批量新增元素
- 基于模板字符串实现批量新增
let str = ''; for(let i = 1; i <= 10 ; i++){ str += `<div>${i}</div>`; } document.body.innerHTML += str; //会导致BODY原始结构中绑定的事件全部小时,所以此操作适用于:原始容器中没有任何内荣,我们把新的内容插入进去
- 文档碎片
let frg=document.createDocumentFragment();//创建文档碎片:装DOM元素的容器 for( let i = 1 ; i <= 10 ; i++){ let divBox = document.createElement('div'); divBox.innerText = i; frg.appendChild(divBox); //最后统一把文档碎片中所有内容放在body末尾,引发一次重排 }
- 修改元素的样式尽可能使用’transform’[translate 位移、scale 缩放、rotate 旋转…]
- 这个属性开启了硬件加速,不会引起重排(回流)
- 如果真的引发重排,也把性能消耗降到最低
- 尽量把修改样式的元素,单独放在一个层面中(脱离文档流),这样即便重排,也只是对着一层的处理
- 基于 JS 实现动画,尽量牺牲平滑度换取速度
2022 年 4 月 18 日
- ES5 之后如果在标签属性名用了’-'都可以使用 dataset 获取属性名
<h1 data-h1 = 'a'><\h1>
,xxx.dataset.h1 == ‘a’; - 项目难点刷新回到最顶部,以前使用
document.documentElement.offsetTop = 0
,现在游览器刷新会记录刷新之前的位置,使用这段代码不能回到游览器顶部 - scrollTop 的设置,并不一定是 document.docuemntElement 也有可能是 document.body,是谁关键是看滚动条是属于哪个元素的
- 防抖就是每次都会把老的定时器给溢出,然后再去重新建立一个新的定时器
let timer = null;
inp.oninput = function () {
//oninput 当文本框的内容发生改变的时候执行这个函数
clearTimeout(timer);
timer = setTimeout(() => {
console.log(this.value);
fetch("./data.json");
}, 1000);
};
- 节流的目的就死为了降低函数触发的评率,设置一个标识,标识会在定时器执行完成之后再去改成代码能正常执行
let throttle = (fn, delay) => {
let flag = true;
return function () {
if (!flag) return;
flag = false;
setTimeout(() => {
flag = true;
}, delay);
fn();
};
};
- 防抖节流的区别: - 防抖的目的是为了让函数虽然会多次执行但是有效代码执行一次; - 节流的目的是为了让函数虽然会多次执行但是有效代码会根据固定评率去执行,当都是函数的有效代码执行一次,节流是为了降有效代码的执行频率
节流最多应用在 scroll 事件和 touchmove 事件
防抖的应用场景一个是按键的连续点击,另一个是 input 的连续输入
11.promise
2022 年 4 年 20 日
- 使用 Promise 的方法:
- . then 方法,用来处理单个 promise 方法返回的成功和失败后的处理,. then()括号中会调用两个函数,第一函数用于处理成功后的事件,第二个函数用来调用失败后的函数 quadratic
如果只想调用失败后的处理方法,可以. then(null,error),可以在第一个函数位置传个 null,它等同于调用 catch 方法:. catch(error)
jscript;
// 创建一个任务对象,该任务立即进入 pending 状态
const pro = new Promise((resolve, reject) => {
// 任务的具体执行流程,该函数会立即被执行
// 调用 resolve(data),可将任务变为 fulfilled 状态, data 为需要传递的相关数据
// 调用 reject(reason),可将任务变为 rejected 状态,reason 为需要传递的失败原因
});
pro.then(
data => {
// onFulfilled 函数,当任务完成后,会自动运行该函数,data 为任务完成的相关数据
},
reason => {
// onRejected 函数,当任务失败后,会自动运行该函数,reason 为任务失败的相关原因
}
);
- then 方法必定会返回一个新的 Promise
- 可理解为
后续处理也是一个任务
- 新任务的状态取决于后续处理:
-
若没有相关的后续处理,新任务的状态和前任务一致,数据为前任务的数据
-
若有后续处理但还未执行,新任务挂起。
-
若后续处理执行了,则根据后续处理的情况确定新任务的状态
- 后续处理执行无错,新任务的状态为完成,数据为后续处理的返回值
- 后续处理执行有错,新任务的状态为失败,数据为异常对象
- 后续执行后返回的是一个任务对象,新任务的状态和数据与该任务对象一
- Promise 的静态方法
方法名 | 含义 |
---|---|
Promise. resolve(data) | 直接返回一个完成状态的任务 |
Promise. reject(reason) | 直接返回一个拒绝状态的任务 |
Promise. all(任务数组) | 返回一个任务 任务数组全部成功则成功 任何一个失败则失败 |
Promise. any(任务数组) | 返回一个任务 任务数组任一成功则成功 任务全部失败则失败 |
Promise. allSettled(任务数组) | 返回一个任务 任务数组全部已决则成功 该任务不会失败 |
Promise. race(任务数组) | 返回一个任务 任务数组任一已决则已决,状态和其一致 |
- 使用方法 Promise. all([括号中需要填写执行数组])
- 将多维数组展开使用. flat()方法
let arry = [
[1, 2, 3],
[1, 2, 3],
[1, 2, 3],
];
console.log(arry.flat()); //[1,2,3,1,2,3,1,2,3];
- 消除回调
有了 Promise,异步任务就有了一种统一的处理方式
有了统一的处理方式,ES 官方就可以对其进一步优化
ES7 推出了两个关键字async
和await
,用于更加优雅的表达 Promise
- async
async 关键字用于修饰函数,被它修饰的函数,一定返回 Promise
async function method1() {
return 1; // 该函数的返回值是Promise完成后的数据
}
method1(); // Promise { 1 }
async function method2() {
return Promise.resolve(1); // 若返回的是Promise,则method得到的Promise状态和其一致
}
method2(); // Promise { 1 }
async function method3() {
throw new Error(1); // 若执行过程报错,则任务是rejected
}
method3(); // Promise { <rejected> Error(1) }
- await
await
关键字表示等待某个 Promise 完成,它必须用于async
函数中
async function method() {
const n = await Promise.resolve(1);
console.log(n); // 1
}
// 上面的函数等同于
function method() {
return new Promise((resolve, reject) => {
Promise.resolve(1).then(n => {
console.log(n);
resolve(1);
});
});
}
await
也可以等待其他数据
async function method() {
const n = await 1; // 等同于 await Promise. resolve(1)
}
如果需要针对失败的任务进行处理,可以使用try-catch
语法
async function method() {
try {
const n = await Promise.reject(123); // 这句代码将抛出异常
console.log("成功", n);
} catch (err) {
console.log("失败", err);
}
}
method(); // 输出: 失败 123
let p1 = new Promise((res, rej) => {
// res rej 是两个函数体,Promise 给的
// rej(777)把 p1 变成了 rejected(失败态)
// res(888)把 p1 变成了 fulfilled/resolved(成功态)
//res 不执行 rej 也不执行 pending(等待态)
// p1 一旦变成了 rejected 或者 fulfilled 那么就不会在去改变
//promise 的实例肯定能由三种状态:原始状态是 pending,成功态 resolved/fulfilled 失败态 rejected
//实例状态:只能由 pending 变成 fulfilled 或者 rejected;不能有 fulfilled 变成 rejected 或者 fulfilled
//该函数报错的时候实例状态也会变成 rejected
});
-
then 的第一个回调函数执行时机:实例状态由 pending 变成 fulfilled 的时候,then 的第一个回调函数的执行接收的实参:res 执行的时候传递的第一个参数
-
then 的第二个回调函数执行时机:实例状态由 pending 变成 rejected 的时候,then 的第二个回调函数的执行接收的实参:rej 执行的时候传递的第一个参数
- 第二个回调函数可以把 rej 的错误信息融掉,不显示在控制台
- 第二个回调函数可以不写
-
catch 对应的回调函数什么时候还行!上面的实例出现失败态的时候
- catch 兜底的错误是没有被上边兜住的错误
-
后面的 then 执行成功函数还是失败函数看上一个 then 对应的回调函数执行有没有出错,后边的 then 的成功回调函数的参数是由上一个 then 中的回调函数的返回值决定的
-
finally 对应的回调会在最后不管前面成功还是失败都会走 finally 对应的回调函数
-
promise 是 js 里面的内置类,promises 是为了方便异步开发,promise 实例有三个状态 pending/fulfilled/rejected
2022 年 4 月 22 日
-
游览器是多线程,但是它只分配一个"JS 引擎线程"用来渲染和解析 js 代码,所以 JS 是单线程的!!
- JS 中大部分代码都是"同步编程",例如:循环…
- 千万不要写死循环,一旦死循环则 JS 引擎会一直被占用,其它事情都做不了
- 遇到程序抛出一场,后面的代码不会再执行
throw new Error;//手动抛出异常
- 我们基于 try{}catch{}进行异常捕获,这样不会影响后续代码执行
- JS 中大部分代码都是"同步编程",例如:循环…
-
JS 中也存在异步编程依托于浏览器多线程,在基于 EventLoop 事件循环机制处理的
- 异步宏任务 macrotask
- 定时器 setTimeout/setInterval
- 事件绑定/队列
- ajax/fetch
- …
- 异步微任务 microtask
- requestAnimationFrame
- promise.then./catch/finally
- async await
- queueMicrotask 基于这个方法可以黄建一个异步微任务
- IntersectionObserver
- …
- 异步宏任务 macrotask
-
游览器打开一个页面,除了开辟堆栈内存,还会默认创建两个队列
- WebAPI 队列:检测异步任务是否可以执行
- EventQueue 队列:存储所有可执行的异步任务,在这个队列中排队等待执行 - 异步微任务 - 异步宏任务
-
定时器到时间后也不一定能执行(设定的时间是其最快的执行时间):如果此时主线程被占用,则必须等主线程空闲下来,排在 EventQueue 中的定时器才可以执行
-
基于 JS 和定时器实现动画效果会存在问题
- 出现卡顿的情况:到时见该走了,但是主线程被占用,它不走了
- 我们设定的时间很难和"屏幕刷新率"保持一致
-
但是可以基于 window.requestAnimationFrame 实现动画
- 不需要设置时间,默认是按照电脑的"屏幕刷新率对应的时间"进行运动的
- 也会出现"因主线程被占用,它无法立即执行"导致卡顿,但是比定时器好,因为他是异步微任务,优先于异步宏任务执行!!
-
-
promise:ES6 新增的内置类(构造函数),用来规划异步编程代码,解决回调地狱问题
-
let p1 = new Promise([executor])
- [executor]必须是一个函数,而且 new Promise 的时候必须把其执行"同步"
- p1 是其创建出来的实例
- 私有属性有
- [[PromiseState]]:“pending”、“fulfilled”、"rejected"实例状态
- [[PromiseResult]]:undefined 实例的值[成功的结果或失败的原因]
- 公共方法:Promise.prototype
- then
- catch
- finally
- …
- p1.then([onfulfilled],[onrejected])
- [onfulfilled]/[onrejected]都是函数
- 实例状态是成功 fulfilled 的时候,会把[onfulfilled]执行,并且把实例的值作为成功的结构传递给他
- 实例的状态是失败 rejected 的时候,会把[onrejected]执行,把实例的值作为失败的原因传递给他
- 私有属性有
- 如何修改实例的状态和值
- 基于这种方式创建实例
let p = new Promise(()=>{...})
- resolve/reject 也是函数
resolve(‘ok’)->把实例 p 的状态修改为 fulfilled,值(成功结果)是’ok’
reject(‘no’)->把实 p 的状态修改为 rejected,值(失败原因)是’no’ - 如果 executor 函数执行报错,则把实例额状态修改为 reject,值是报错原因[不会抛异常]
- 一旦状态被修改为 fulfilled 或 rejected,后期就不会在更改状态值了
- resolve/reject 也是函数
- 每一次执行 then 方法,都会返回一个"全新的 prominse 实例"
let p2 = p1.then(onfulfilled,onrejected);
不论是 onfulfilled 还是 onrejected 执行(由 p1 状态决定),方法的执行决定了 p2 的状态和值
- 首先看方法执行是否报错,如果报错了,则 p2 是失败态(rejected),值是报错原因
- 如果执行不报错,再看方法的放回值
- 如果返回的是"新的 promise 实例->@np,则@np 的状态值直接决定了 p2 的状态和值"
- 如果返回的不是新实例,则 p2 撞他是成功(fulfilled),值是函数返回值
- 执行 Promise.resolve/reject/all/any/race…等静态私有方法,也会创建新的 promise 实例
- Promise.resolve(10)创建一个状态是成功 fulfilled,值是 10 的实例
- Promise.reject(0)创建一个状态是失败 rejected,值是 0 的实例
- 基于这种方式创建实例
p1.then( value =>{ console.log('成功',value) },reason => { console.log('失败',reason) })
-
then 链的穿透/顺延机制
- .then(onfulfilled,onrejected),两个方法可以传可以不传,如果不传则顺延至下一个 then 中,相同状态执行的方法中去处理!
- 原理:我们不设置对应的方法,promise 内部会默认加一个方法,可以让其实现状态顺延/穿透
p.catch(onrejected)等价与p.then(null,onrejected)
- 原理:我们不设置对应的方法,promise 内部会默认加一个方法,可以让其实现状态顺延/穿透
- .then(onfulfilled,onrejected),两个方法可以传可以不传,如果不传则顺延至下一个 then 中,相同状态执行的方法中去处理!
-
真实项目中:then 中一般只传递 onfulfilled[成功干什么],最后一个 catch;这样不论中间哪个环节创建了失败的实例,都会穿透至最后一个 catch;catch 不加,出现失败案例,控制台报"红",但是不影响其他代码执行!!
-
关于 Promise.all/any/race 第三个方法的研究
let p = Promise.all([promises]);
- Promise 是包含零到多个 primise 实例的集合,一般是数组!如果集合中的某一项不是 promise 实例,则默认变为状态为成功,值是本身的 promise 实例!!
- all:集合中的"每个实例都成功",最后结果 p 才成功,值是按照结合顺序,一次存储每个实例成功结果的数组;其中只要有一个实例失败,则 p 就失败的,值是本次失败的原因,后面的操作不再处理!!
- any:只有一个成功,最后 p 就是成功的,值是本次成功的结果;都失败,最后 p 才是失败!{兼容性不好}
- race:集合中谁最先指导结果,则以谁的为主!
-
AJAX 的串行和并行:真实项目中发送 ajax 请求都是"采用异步编程"
- 并行:多个请求通水发送即可,谁先回来先处理谁,主要用于多个请求间没有依赖[偶尔我们需要检测,多个请求都成功,整体再去做啥事 => Promise.all]
- 串行:多个请求之间存在依赖,上一个请求成功,我们才能发送下一个请求(往往是下一个请求需要用到上一个请求的结果,才需要这样处理)!