1.基础面试题
1.延迟加载JS的方式有哪些
定时器/async/defer 这里主要介绍async/defer原理
从图上可以看出。
1.普通script脚本,
会阻塞html文件的解析,当遇到script时,停止html解析,去下载脚本且执行脚本,然后再去解析html文件
2.async 脚本
脚本的下载和html的解析是并行的,下载不会阻塞解析。但是下载完成后会立即执行脚本,阻塞解析。执行完成后继续解析剩下的html文件
3.defer 脚本
脚本的下载和html的解析是并行的,下载不会阻塞解析。且执行时机是在html解析完成后在执行
相同点
加载文件时不阻塞页面渲染
对于 inline 的 script 无效(只适用有src的外部 js) <script async src="./1.js"></script>
区别点
每一个 async 属性的脚本都在它下载结束之后立刻执行,谁先下载完谁先执行,所以就有可能出现脚本执行顺序被打乱的情况;
每一个 defer 属性的脚本都是在页面解析完毕之后,按照原本的顺序执行
推荐的应用场景
defer
如果你的脚本代码依赖于页面中的DOM元素(文档是否解析完毕),或者被其他脚本文件依赖。
async
如果你的脚本并不关心页面中的DOM元素(文档是否解析完毕),并且也不会产生其他脚本需要的数据。
如果不太能确定的话,用defer总是会比async稳定。
2.JS数据类型有哪些
1.基本类型
string、number、boolean、undefined、null(typeof null --> object)、symbol
2.引用类型
Object(Object是个大类 function、array、date都归属于object)(typeof function -->function)
3.隐士转换
【Js】JavaScript数据类型隐式转换_前端_快乐水续航-华为云开发者联盟
1.隐式类型转换的方式
- 将值转为布尔值,Boolean()
- 将值转为数字,Number()
- 将值转为字符串,toString()
2.部分常见值的转化结果
1、Boolean类型. 通过Boolean()转化
Object:任意对象为true,null/undefined/0/NaN/''为false;
2、Number类型. 通过Number()转化
会转化为NaN的值:undefined,带字母或符号的字符串,所有引用类型(数组,对象,function,date等)
''转为0
[1]-->1,[1,2]-->NaN,[]--->应该是执行了toPrimitive(number)
特例--null:0
3、String类型
String(),null:“null”,undefined:“undefined”,其他调用toString()方法
{a:1}.toString--->'[object Object]'
特例:new String(1234).toString()
'1234'
3. 逻辑运算符( && , ||)
(转为boolean类型)
- &&
会将第一个操作数转化为Boolean类型
若转化后第一个操作数为true,则返回第二个操作值(可能不成立);否则返回第一个操作数的原始值(一定是不成立)
(返回可能不成立和一定不成立的原始值)
// 部分示例
1 && "key" //key
"key" && "" // ""
null && "key" // null
undefined && 0 // undefined
- ||
若第一个操作值经转化后为false则返回第二个值(可能成立),否则返回第一个的原始值(一定成立)
(返回可能成立和一定成立的原始值)
1 || "key" //1
"key" || "" // "key"
null || "key" // "key"
undefined || 0 // 0
4.==
(两边类型不同转为Number类型toPrimitive(number))
==,等同的意思,两边值类型不同的时候,要先进行类型转换为同一类型后,再比较值是否相等。 ===,恒等的意思,不做类型转换,类型不同的结果一定不等...
"“表示只要值相等即可为真,而”="则要求不仅值相等,而且也要求类型相同
1.null与undefined双等比较或与各自与自身三等比较,结果是true,与其他值比较都为false。
- 类型不同的情况
- 如果两边类型不同,则两边都尝试转成number类型。
- 对于引用类型,先调用
valueOf()
,如果能转成数字(原始值),则进行比较。不能转成数字就调用toString()
方法转成字符串。(ToPrimitive(number))
var a = new String(123)
console.log(a==123) //true,a.valueOf()结果就是"123",最终比较的是"123"==123 => 123 == 123
var a = {}
console.log(a == 1)
//上面a==1在js解释引擎中的执行过程如下:
//a.valueOf()获取到的不是基本类型,调用a.toString()得到'[object Object]'
'[object Object]'==1;
//两边类型不致,左侧转成数字
NaN==1;//false,NaN跟任何类型比较都为false
- null、NaN、undefined
null、NaN、undefined和string、number、boolean、object类型比较时,都不做隐式转换,比较的结果直接为false。但是需要注意以下几个规则:
console.log(NaN==NaN) //false
console.log(undefined==null) //true
console.log(null==null) //true
console.log(null==undefined) //true
undefined == undefined //true
console.log(null===null) //true
undefined === undefined //true
console.log(undefined===null) //false
- 类型相同
-
- 基本类型,直接比较值
- 引用类型比较指针
- 类型不同,尝试转成number类型,
-
- 先调用valueOf()转成number
- 不行就再用toString()方法转成string
- null、NaN、undefined单独一套规则
4、复杂数据类型的转换规则
‼️‼️‼️‼️⚠️
复杂对象如何转换为简单值
一个复杂对象在转为基础类型的时候会调用ToPrimitive(hint)方法来指定其目标类型。
如果传入的hint值为number,那么就先调用对象的valueOf()方法,调用完valueOf()方法后,如果返回的是原始值,则结束ToPrimitive操作,如果返回的不是原始值,则继续调用对象的toString()方法,调用完toString()方法之后如果返回的是一个原始值,则结束ToPrimitive操作,如果返回的还是复杂值,则抛出异常。
如果传入的hint值为string,则先调用toString()方法,再调用valueOf()方法,其余的过程一样。
那么复杂对象是以什么标准来判断 ToPrimitive(hint) 操作传入的hint值到底是number还是string 呢?
- 如果运行环境非常明确的需要将一个复杂对象转换为数字则传入number如 Number(value) 和 +value 则传入number
- 如果运行环境非常明确的需要将一个复杂对象转换为字符串则传入string如String(value) 和 alert(value) 则传入string
- 如果是用+号连接两个操作数,操作数在确定其中只要有一个为字符串的时候另外一个操作数会转为字符串,ToPrimitive()会传入string,但是如果两个操作数都不能确定是字符串的时候则默认传入number(Date对象是一个例外,它会默认传入string)进行数据类型转换。
调用valueOf()返回的结果:
Array | 返回数组本身 |
Boolean | 布尔值 |
Date | 存储的时间是从1970年1月1日午夜开始级的毫秒数UTC,eg:(new Date()).valueOf() -->1626695004310 (相当于调用了getTime()) |
Function | 函数本身 |
Number | 数字值 |
Object | 对象本身(这是默认情况) |
String | 字符串 |
undefined、Null对象没有valueOf方法(Math、Error貌似有) |
调用toString()返回的结果:
Array | 以逗号分隔每个数组形成的字符串,约等于调用.join() |
Boolean | “true”或“false” |
Date | “Mon Jul 19 2021 18:46:05 GMT+0800 (中国标准时间)” |
Function | 函数的文本定义 |
Number | 数字转成的字符串如"123" |
Object | “[object Object]” ,特例:用new关键字加上内置对象创建的Object类型数据,调用toString。eg: (new String(‘abc’)).toString() ==> ‘abc’ (相当于先给他拍成对应的基础数据类型再调用toString方法) |
String | 字符串本身 |
5、 加号+
1.当+作为一元操作符时,相当于执行Number()
2.当+作为二元操作符时
- 第一步:如果A和B都是number类型,直接相加;
- 第二步:接下来看A和B两个操作数是否有一个是string类型,存在一个字符类型的话,那么另外一个操作数也会无条件地转换为字符串,然后连接;
- 第三步:.如果一侧是引用类型,则引用转为number/string,另一边不变
计算 [1,2,3] + 1的过程:
[1,2,3].valueOf() => [1,2,3]
[1,2,3] + 1
[1,2,3].toString() + 1;
'1,2,3' + 1
(此时得到了字符串,则去匹配加号两边又一边为字符串的规则)
"1,2,31"
{}被当做了代码块
>{}+[]
>0
{a:1}+[]--->1
1+{}--->'1[object Object]'
如果其中一个是字符串,另一个也会被转换为字符串,否则两个运算数都被转换为数字。
而同时,javascript有这样的特性,如果{}既可以被认为是代码块,又可以被认为是对象字面量,
那么js会把他当做代码块来看待。
这就很好解释了,{}被当做了代码块,只有+[],根据加法的定义,被转换为0,就得到了结果。
6、 感叹号!
!会将后面的数据先转成布尔值,然后取反
3.null和undefined的区别
js一开始借鉴java设计了null(typeof null --->object).代表‘无’的值,且null可以被隐士转为0(Number(null)--->0)
null在java中被当做一个空指针对象,但是js分为基本类型和引用类型,觉得代表‘无’的值最好不要是对象,且数据类型不匹配时转为0,不容易发现问题,所以又设计了一个undefined
先有null后有undefined,出来undefined是为了填补这个坑的
4.js微任务/宏任务
JS事件循环(Event Loop)_js eventloop_藏锋de刃的博客-CSDN博客
1.同步模式和异步模式:
javascript是单线程的,意思就是执行完上一个才能取执行下一个,如果有一个任务执行时间很长(请求资源),那么后面的任务都需要等待它完成了才能继续去执行,就会发生阻塞。
“ 任务队列 " 是一个先进先出的数据结构,排在前面的事件,优先被主线程读取。主线程的读取过程基本上是自动的,只要执行栈一清空," 任务队列 " 上第一位的事件就会自动进入主线程。
为了解决阻塞问题,javascript将任务的执行模式分为同步模式(sync)和异步模式(async)
同步模式就是js主线程的执行完上一个任务才能继续去执行下一个任务,按顺序的
异步模式就是任务有一个回调函数,当这个任务触发后将回调函数放入task queue中
2.事件循环(Event Loop):
同步任务和异步任务分别进入不同的场所,同步任务在主线程执行,异步任务进入Event Table注册回调函数与触发的方式
当指定的事情完成(如ajax请求响应返回, setTimeout延迟到指定时间)后,Event Table会将对应的回调函数放入task queque中,等待主线程的任务都执行结束后,就从任务队列中以先进先出的方式去执行task queue里面的任务
如此循环就形成了js的Event Loop
3.宏任务(macro-task)/微任务(micro-tsak)
任务队列分为宏任务(macro-task)和微任务(micro-tsak).分别存放在不同的task Queue中
macro-task(宏任务):
- 整体代码script(原理不懂❓❓❓)
- setTimeout
- setInterval
- setImmediate
- I/O (比如Ajax操作从网络读取数据)
- UI render
micro-task(微任务):
- process.nextTick
- Promise.then
- Async/Await(实际就是promise)
Scpirt(整体代码)也是一个宏任务,整体代码执行完后,会先查看micro-task的队列中是否有需要执行的任务,如果有就去执行,直到执行微任务队列为空后,再去macro-task的队列中执行第一个宏任务,执行结束后在去检查微任务队列是否有需要执行的,就这样循环下去,直到task Queue为空
4.事件表格(Event Table):
Event Table可以理解为一张 事件-->回调函数 对应表
当指定的事件完成后(a ja x请求响应返回,setTimeOut延迟到指定时间),Event Table会将对应的回调函数移到task Queue中
5.整体script算是宏任务🌰
<!-- 脚本 1 -->
<script>
// 同步
console.log('start1')
// 异步宏
setTimeout(() => console.log('timer1'), 0)
new Promise((resolve, reject) => {
// 同步
console.log('p1')
resolve()
}).then(() => {
// 异步微
console.log('then1')
})
// 同步
console.log('end1')
</script>
<!-- 脚本 2 -->
<script>
// 同步
console.log('start2')
// 异步宏
setTimeout(() => console.log('timer2'), 0)
new Promise((resolve, reject) => {
// 同步
console.log('p2')
resolve()
}).then(() => {
// 异步微
console.log('then2')
})
// 同步
console.log('end2')
</script>
start1
p1
end1
then1
start2
p2
end2
then2
timer1 timer1最后执行。说明了script是一个宏任务,因为宏任务执行过后就会去执行微任务,将setTime又产生了一个宏任务
等到两个script(并排第一第二的宏任务)都结束后,产生了两个setTime宏任务,在一次去执行
timer2
setTimeout(() => {
// 同步
console.log('start1')
// 异步宏
setTimeout(() => console.log('timer1'), 0)
new Promise((resolve, reject) => {
// 同步
console.log('p1')
resolve()
}).then(() => {
// 异步微
console.log('then1')
})
// 同步
console.log('end1')
}, 0)
setTimeout(() => {
// 同步
console.log('start2')
// 异步宏
setTimeout(() => console.log('timer2'), 0)
new Promise((resolve, reject) => {
// 同步
console.log('p2')
resolve()
}).then(() => {
// 异步微
console.log('then2')
})
// 同步
console.log('end2')
}, 0)
start1
p1
end1
then1
start2
p2
end2
then2
timer1
timer2
综上,两个结果是一样的,所以证实了 <script></script>
整体跟 setTimeout
一样是宏任务。
6.例子🌰
Promise.resolve().then(() => {
console.log("4")
setTimeout(() => {
console.log("22")
Promise.resolve().then(() => {
console.log("8")
})
}, 0)
console.log("6")
})
setTimeout(() => {
console.log("2")
Promise.resolve().then(() => {
console.log("3")
setTimeout(() => { 算是当前微任务下的任务队列的宏任务
console.log("33")
}, 0)
Promise.resolve().then(() => {
console.log("5")
})
})
console.log("7")
}, 0)
4 6 2 7 3 5 22 8 33
全部微任务执行完才能去 执行宏任务,即使微任务中嵌套着微任务。
5.JS作用域/变量提升
1.函数式声明优先级高于var声明
在 JavaScript 中,函数的变量提升优先级高于使用 var 声明的变量。这意味着在同一个作用域中,函数声明会覆盖变量声明。
function a(){
console.log(222)
}
var a
console.log(a) 输出 fna. 说明函数声明优先级高于var声明
--------------- --------------- --------------- --------------- --------------- ---------------
function a(){
console.log(222)
}
var a =20
console.log(a) 输出 20
--------------- --------------- --------------- --------------- --------------- ---------------
var a= function(){
console.log(222)
}
var a
console.log(a) 输出 fn. 函数声明方式会提升
函数表达式(匿名函数) 不会提升
--------------- --------------- --------------- --------------- --------------- ---------------
2.函数参数的优先级高于var声明低于函数式声明(值不会提升)
function a(b){
var b
console.log(b)
b=3
}
a(2) 输出 2
--------------------- --------------- --------------- --------------- --------------- ---------------
function a(b){
var b=20
console.log(b)
}
a(2) 输出 20
--------------------- --------------- --------------- --------------- --------------- ---------------
function a(b){
function b(){}
console.log(b)
}
a(2) 输出 fnb
--------------------- --------------- --------------- --------------- --------------- ---------------
function a(b){
var b
console.log(b)
function b(){}
}
a(2) 输出 fnb
不通过关键词 var
创建的变量总是全局的,即使它们在函数中创建。
3.let同名的变量只能定义一次
function a(b){
let b Identifier 'b' has already been declared报错
console.log(b)
}
a(2)
6.原型链
每一个JavaScript对象(除了 null )都具有的一个属性,叫proto,这个属性会指向该对象的原型
函数的 prototype 属性指向了一个对象,这个对象正是调用该构造函数而创建的实例的原型
每一个构造函数都有prototype属性指向一个对象(Person.prototype),该构造函数的实例的原型(new Person().__proto__)就是这个对象
Person.prototype===new Person().__proto__
这个原型对象的constructor属性指向它的构造函数
Person.prototype.constructor===Person
所以 person.constructor===Person
这个原型对象是new Object()生成的,所以,这个原型对象的原型就是Object.prototype
person.__proto__.__proto__===Object.prototype
Object.prototype的constructor指向Object
person.__proto__.__proto__.constructor===Object
在往上Object.prototype的原型就是null
Test.prototype.__proto__.__proto__==null true
function A(){
}
const a=new A()
a.__proto__.abx=20
console.log(a)
console.log(A.prototype.abx) 20
JS的大原型其实是Object
构造函数的prototype
指向的那个Object
,JS的世界的来源就是它。
7.this指向
- 普通函数调用,this指向window
- 构造函数调用,this指向实例对象
- 对象方法调用,this指向 该方法所属的对象
- 定时器函数,this 指向window
1.在非箭头函数下
在非箭头函数下, this 指向调用其所在函数的对象,而且是离谁近就是指向谁
1.普通函数执行
普通函数执行,其中的this指向window。因为这个函数是被window调用的
function Fn(){
console.log(this)
}
1. Fn() //正常执行this指向window ---》window.Fn()
2. new Fn() //new 的话this指向构造函数生成的实例对象
2.对象中的this
对象内部方法的this指向调用这些方法的对象,
1. 函数的定义位置不影响其this指向,this指向只和调用函数的对象有关。
//1
var o = {
prop: 37,
f: function() {
return this.prop;
}
};
console.log(o.f()); //37
var a = o.f;
console.log(a()): //undefined
var o = {prop: 37};
function independent() {
return this.prop;
}
o.f = independent;
console.log(o.f()); // logs 37
//2
o.b = {
g: independent,
prop: 42
};
console.log(o.b.g()); // logs 42
构造函数中的this与被创建的新对象绑定。
注意:构造器返回的默认值是一个this引用的对象,可以手动设置返回其他的对象,如果返回值不是一个对象,返回this。
在 JavaScript 中,构造函数默认会返回一个 `this` 引用的对象。这意味着构造函数在被调用时,会自动创建一个新的对象,并将该对象作为构造函数的返回值。
如果在构造函数中显式地使用 `return` 语句并返回一个对象,那么该对象将成为构造函数的返回值。如果返回值不是一个对象,而是一个基本数据类型(如字符串、数字或布尔值),那么构造函数仍然会返回 `this` 引用的对象。
以下是一些示例来说明构造函数返回值的情况:
```javascript
function Person(name, age) {
this.name = name;
this.age = age;
// 没有显式返回值,默认返回this
}
var person1 = new Person("John", 25);
console.log(person1); // 输出 Person { name: 'John', age: 25 }
function Car(make, model) {
this.make = make;
this.model = model;
return { make: "Default", model: "Default" };
}
var car1 = new Car("Toyota", "Camry");
console.log(car1); // 输出 { make: 'Default', model: 'Default' }
function Animal() {
this.name = "Unknown";
return 42;
}
var animal1 = new Animal();
console.log(animal1); // 输出 Animal { name: 'Unknown' }
```
在上述示例中,`Person` 构造函数没有显式返回值,因此默认返回 `this` 引用的对象。`Car` 构造函数使用 `return` 语句返回一个新的对象,覆盖了默认的返回值。而 `Animal` 构造函数返回的是一个基本数据类型值而不是对象,但仍然返回了 `this` 引用的对象。
总结:
- 构造函数默认返回 `this` 引用的对象。
- 如果构造函数中显式使用 `return` 语句并返回一个对象,那么该对象将成为构造函数的返回值。
- 如果返回值不是一个对象而是一个基本数据类型,构造函数仍然会返回 `this` 引用的对象。
2.原型链中的方法的this仍然指向调用它的对象,
3.构造函数中this( 返回值不是一个引用类型,则返回this)
function C(){
this.a = 37;
}
var o = new C();
console.log(o.a); // logs 37
function C2(){
this.a = 37;
return {b:38};
}
var b = new C2();
console.log(b); //{b:38} 手动设置返回其他的对象
function C2(){
this.a = 37;
return 39;
}
var b = new C2();
console.log(b); //{a:37} 返回值不是一个引用类型(【】,function,{}),则返回this。
2.定时器内部的this
setTimeout 这个方法是挂载到 window 对象上的。setTimeout 执行时,执行回调函数,回调函数中的 this 指向调用 setTimeout 的对象,window
定时器内部的this永远指向window(不包含箭头函数)
setTimeout(function(){
console.log(this);window
},1000)
function Person() {
this.age = 0;
setInterval(function(){
console.log(this) window
}, 3000);
}
var p = new Person();
var obj = {
id: "1",
cool: function coolFn() {
console.log(this.id);
console.log(this);
}
};
var id = "2"
setTimeout(obj.cool, 100);
//此时打印出来的结果是2和全局对象
setTimeout(()=>obj.cool(), 100);和上面同样的结果,因为是执行了obj.cool
3.箭头函数中的 this
在箭头函数中,this 的值是根据函数的定义时的上下文确定的,而不是根据函数的调用方式确定的。箭头函数会捕获外部作用域的 this 值,并在函数体内部使用。
由于箭头函数不绑定this, 它会捕获其所在(即定义的位置)的外层第一个普通函数的this, 作为自己的this值
如果外层还是箭头函数则继续向外捕获
向外捕获
let obj={a:1}
function nm(){
return ()=>{
console.log(this,1)
return ()=>{
console.log(this,2)
}
}
}
obj.fn=nm
obj.fn()()()
let local={
set:()=>{
console.log(this) window
}
}
local.set()
function Person() {
this.age = 0;
setInterval(() => {
console.log(this) p。 当前this在定义函数时就确定为Person函数的this
}, 3000);
}
var p = new Person();
function Person() {
this.age = 0;
setInterval(() => {
console.log(this) window
}, 3000);
}
Person();
let onk={
b:function(){
console.log(this)//onk
let obj={
fn:function(){
console.log(this)
},
callB:()=>{
console.log(this) //外层第一个普通函数的this
}
}
obj.fn() //obj
obj.callB()//onk
}
}
onk.b()
//箭头函数的this在定义时就确定好了的
let onk={
b:function(){
console.log(this)//onk
// return ()=>{
// console.log(this) //onk
// }
return function(){
console.log(this) //window
}
}
}
onk.b()()
❗️🌰
const obj={
id:'1',
fn:function(){
console.log(this)
}
}
obj.fn()//{id: '1', fn: ƒ}
setTimeout(obj.fn,1000)//window //只相当于把函数放在这里,形如let a=obj.fn;a()
setTimeout(()=>obj.fn(),1000)//{id: '1', fn: ƒ}
8.判断变量是不是数组
方法
1. console.log(Array.isArray([]))
//它会检查右边构造函数的原型对象(prototype),是否在左边对象的原型链上 x.__proto__===new Object().prototype
2. console.log([] instanceof Object) //true Object在【】原型链的顶端
console.log([] instanceof Array) //true
console.log(typeof [].constructor) //Array函数本身
console.log(Array) //ƒ Array() { [native code] } //包装类
3. console.log( [].constructor.toString()) //转为字符串的函数//string 函数的tostring也被重写过了
//isPrototypeOf是原型链上的方法
//用于判断一个对象(Array.prototype)是否是另一个对象的原型对象
4. console.log(Array.prototype.isPrototypeOf([]))
5. Object.prototype.toString.call([1,2]) --->[object Array]
6.XXX.constructor.name (1.constructor.name--->'Number')
Object.prototype.toString.call([1,2]) --->[object Array]
//js的数组的toString是被重写过的,所以不能用[1,2].toString()
//我来实现一遍
function Name( ){
this.toString=function(){
return `[object ${this.constructor.name}]`;
}
}
let name=new Name([])
console.log(name.toString.call([])) --->[object Array]
Object.prototype.toString()方法大概就是这么写的,获取调用它的对象的this
根据下面的手写实现call方法
Object.prototype.toString.call()
在call中。fn就是Object的toString方法,而Array的toString是被重写过的
所以传入数组.call([]),就是[].toString().执行的是最原始的方法。所以会输出 --->[object Array]
包装类
JavaScript中内置类(构造函数)Number、String、Boolean、Function、Array因为都是继承Object,所以下面的输出也都是true:
typeof Number--->'function'
Object也是函数(类):
console.log(Object.prototype.isPrototypeOf(Number)); // true
console.log(Object.prototype.isPrototypeOf(String)); // true
console.log(Object.prototype.isPrototypeOf(Boolean)); // true
console.log(Object.prototype.isPrototypeOf(Array)); // true
console.log(Object.prototype.isPrototypeOf(Function)); // true
————————————————
自然Object.prototype也是Number、String、Boolean、Function、Array的实例的原型。
Object.prototype.isPrototypeOf([])---》true
------
JavaScript的原始类型并非对象类型,所以从理论上来说,它们是没有办法获取属性或者调用方法的。'abc'.slice
原始类型是简单的值,默认并不能调用属性和方法;
这是因为JavaScript为了可以使其可以获取属性和调用方法,对其封装了对应的包装类型;
console.log(Array) //ƒ Array() { [native code] } //包装类
9.call ,apply和bind方法
Object.prototype.my=function(){
console.log(this)//谁去调用这个函数,这个函数中的this就是谁
}
let a={a:1}
a.my()
1.Function.prototype.call
call 方法允许你显式指定函数的执行上下文(即 this 值),并立即调用该函数
//主要利用的是谁去调用这个函数,这个函数中的this就是谁,可以将test这个函数获取到
const obj = {
name: '张三'
};
Function.prototype.myCall=function(obj){
// .myCall内部将【第一个参数(this将指向的对象)】身上【添加了一个方法】,【添加的方法】就是【调用myCall的方法】
let object=obj || window;
object.fn=this; //主要利用的是谁去调用这个函数,这个函数中的this就是谁。所以将test这个函数获取到了
const [,...args]=arguments;
//然后用这种方式执【添加的方法】:【第一个参数.添加的方法】。并保存他的返回值。
let res= object.fn(...args)
// 执行完【添加的方法】后要删除【添加的方法】.否则会给这个传入的对象增加一个fn属性,如果其他地方有用到这个对象,是共用的
delete object.fn;
return res
}
function test(){
console.log(this)
}
test() //window
test.myCall(obj,1,2,3) //{name:'张三',fn:test}
2.Function.prototype.apply
第二个参数是一个数组
apply的形成和call除了传参不同,其他一模一样
test.myCall(obj,[1,2,3])
3.Function.prototype.bind 未完待续
//1.返回一个新的函数
//2.这个函数的this指向绑定的对象
//3.函数符合柯里化的规则
//4.新函数的this指向无法再被修改,使用apply,call也不行
Function.prototype.myBind=function(obj){
const object=obj || window
object.fn=this
const [,args]=arguments
//关键就是返回一个函数
return function(){
const [...args2]=arguments
//且在函数里执行了被绑定的初始函数,所以即使再call,apply也没有用(没有用到this)
object.fn(...(args.concat(args2)))
}
}
var num=50;
const obj1={
num:100
}
const obj2={
num:200
}
function test(x,y){
console.log(this.num)
console.log(x,y)
}
test() //50,undefined,undefined
const fn=test.bind(obj1,90)
fn(100) //100,90,100 //3.函数符合柯里化的规则
const fn1=test.bind(obj1)
fn.call(obj2) //100,undefined,undefined //4.新函数的this指向无法再被修改,使用apply,call也不行
10.数组方法
forEach(没有返回值,不改变原数组,只对每个元素进行操作)
map(返回新数组,不改变原数组,新数组长度不变)
some(返回boolean,有一个满足条件则返回true,都不满足返回false)
every(返回boolean,有一个不满足条件则返回false,都满足返回true)
filter(返回符合条件的新数组,长度可变化,原数组不变) arr.filter(Boolean)-->Boolean(item)筛选掉false得元素
find(返回满足条件的第一个元素)/findIndex
Join/toString(数组的toString被重写了,相当于join)
for in (key)
for of(值)(对象不可用)
slice(包括,不包括)返回被删除的元素组成的数组,不改变原数组,只写一个,代表包括这个后面的全部切掉,切片
splice(包括,删除个数,增加的元素)改变原数组,返回被删除的元素组成的数组,只写一个同slice,拼接
[1,2,3,4,5,6,7].slice(-3)--->[5,6,7]
[1,2,3,4,5,6,7,8,9,0].slice(3)--->[4,5,6,7,8,9,0]
concat 返回新数组,不改变原数组,可以传入多个参数,数组和单个元素都可以arr.concat([2,3,'o'],[23],'123'))
reduce 返回一个累计值。如果写了第二个参数那么回调函数的第一个参数就是这个,第二个参数是数组第一项。
如果不写第二个参数,第一个参数就是数组第一项,第二个参数是数组第二项
reduce((all,item,index,arr)=>{},'')
sort 会改变原数组,并返回修改后的原数组。回调函数不写则默认Unicode排序。
array.sort(compareFunction)
比较函数的定义:
- 如果 `compareFunction(a, b)` 返回一个小于 0 的值,则 `a` 排在 `b` 前面。
- 如果 `compareFunction(a, b)` 返回一个大于 0 的值,则 `b` 排在 `a` 前面。
- 如果 `compareFunction(a, b)` 返回 0,则 `a` 和 `b` 的相对位置不变。
reverse 反转 会改变原数组,并返回修改后的原数组。
数组去重
continue可以跳出本次循环去进入下一次循环,break直接终止这整个循环,都会不执行当前循环的剩余代码且适用于for in/of
1.需要另一个数组
let array = [1, 2, 3, 4, 5, 6, 71, 1, 34, 3, 4,5]
let b = []
// for (let i = 0; i < array.length; i++) { //双for
// let flag=true
// for (let j = 0; j < b.length; j++) {
// if(array[i]==b[j]){
// flag=false
// break;
// }
// }
// if(flag){
// b[b.length]=array[i]
// }
// }
// for (let index = 0; index < array.length; index++) {
// if(b.indexOf(array[index])<0){ //indexOf
// b[b.length]=array[index]
// }
// }
// for (const key in array) {
// if(b.indexOf(array[key])<0){ //for in+indexOf
// b[b.length]=array[key]
// }
// }
// b = array.filter((item, index) => { //filter+indexOf
// return array.indexOf(item) ==index
// })
// b=[...new Set(array)] === //set
array=array.sort()
for (let index = 0; index < array.length; index++) {
if(array[index]!==array[index-1]){ //for+sort
b[b.length]=array[index]
}
}
console.log(b)
2.不需要
// let array = [1, 2, 3, 4, 5, 6, 71, 1, 34, 3, 4,5]
for (let index = 0; index < array.length; index++) { //双for+splice
for (let j = index+1; j < array.length; j++) {
if(array[index]===array[j]){
array.splice(j,1)
}
}
}
console.log(array)
3.对象去重
相同的对象数组去重
const arr=[{a:1},{a:2},{a:1}]
[...new Set(arr.map(item=>JSON.Stringfy(item))]
.map(item=>JSON.parse(item))
JSON.stringify({a:1})--->'{"a":1}'
11.new操作符做了什么
//1.创建一个空对象
//2.构造函数的原型在空对象的原型链上
//3.构造函数的this指向空对象(使空对象获取到构造函数的属性)
//4.对构造函数的返回值进行判断
function Fn(name,value){
this.name=name;
this.value=value
// return [1,2]
}
function create(fn,...args){
//1.创建一个空对象
let obj={};
//2.构造函数的原型在空对象的原型链上
Object.setPrototypeOf(obj,fn.prototype) //设置obj.__proto__===Fn.prototype
//3.构造函数的this指向空对象
const result= fn.apply(obj,args)
//4.对构造函数的返回值进行判断
return result instanceof Object ? result : obj //返回的如果是基本类型则返回空对象,如果是设置的对象则返回设置的对象
//默认都是不返回的,不返回则打印Fn()为undefined。还是基本类型
}
console.log(create(Fn,2,3)) //Fn {name: 2, value: 3}
console.log(create(Fn,2,3).__proto__===Fn.prototype) //true
12.闭包
一、JavaScript 核心进阶:导读 - 简书
只有内部函数访问了上层作用域链中的变量对象时,才会形成闭包
let obj={
// fn:function(){ //bind
// setTimeout(function(){
// console.log(this)
// }.bind(this))
// }
// fn:function(){ //箭头函数
// setTimeout(()=>{
// console.log(this)
// })
// }
// fn:function(){ //闭包
// let self=this
// setTimeout(function(){
// console.log(self)
// })
// }
// fn:function(){ //闭包
// (function(a){
// setTimeout(function(){
// console.log(a)
// })
// })(this)
// }
}
obj.fn()
13.排序
1.冒泡排序
//相邻元素比大小,交换位置,将最大的放到最后
// let arrBubble = [1, 6, 3, 4, 5];
// debugger
// for (let index = 0; index < arrBubble.length; index++) {
// for (let j =index+1 ; j < arrBubble.length; j++) {
// if(arrBubble[index]>arrBubble[j]){
// let a= arrBubble[index]
// arrBubble[index]=arrBubble[j]
// arrBubble[j]=a
// }
// }
// }
2.快速排序
基准(第一个元素)+递归
// let arrBubble = [1, 6, 3, 4, 5];
// function quickSort(arr){
// //小于等于1.少走一遍
// if(arr.length<=1){
// return arr
// }
// const jizhun=arr[0];
// let left=[];
// let right=[];
// //从1开始
// for (let index = 1; index < arr.length; index++) {
// arr[index]<jizhun
// ?left.push(arr[index])
// :right.push(arr[index])
// }
// //[...[],1]--->[1]
// return [...quickSort(left),jizhun,...quickSort(right)]
// }
3.选择排序
将第一个元素当作miniIndex与后面的做比较,
如果miniIndex的元素大于后面的,则将后面的index作为miniIndex
如果内层循环结束一次,miniIndex不等于外层index,那么外层当前index的值等于miniIndex的值
// let arrSelect = [1, 6, 3, 4, 5];
for (let index = 0; index < arrSelect.length; index++) {
let minIndex=index;
for (let j = index+1; j < arrSelect.length; j++) {
if(arrSelect[j]<arrSelect[minIndex]){
minIndex=j;
}
}
if(minIndex!==index){
let a=arrSelect[i];
arrSelect[i]=arr[minIndex]
arr[minIndex]=a
}
}
14.深/浅拷贝
浅拷贝
原理:1.对属性值为基本类型做一个值的复制(改变这个值,源对象不会改变)
2.对属性值为引用类型则做一个地址的复制(改变这个地址中的属性的值,源对象也会改变)
1.Object.assign()
let ui = {
a: 1,
b: 2,
c: {
d: 20
}
}
let nm = ui;
nm.a = 20
//它将返回目标对象
let qo = Object.assign({}, ui)
//Object.assign拷贝的是源对象的属性值
//如果源对象的属性值是基本类型,则为深拷贝。
qo.b = 100
//如果为引用类型,则拷贝的是属性值的引用,则为浅拷贝
qo.c.d = 90
console.log(qo)
console.log(ui, nm)
//如果不写源对象,只写目标对象
let q1 = Object.assign(ui)
console.log(q1 === ui) //true //因为他返回的目标对象
2.slice/...
let arr = [1, 23, 4, { k: 10 }]
let jk = arr.slice()
// let jk=[...arr]
jk[0] = 20 //值是基本类型,则为深拷贝。
jk[3].k = 100 //为引用类型,则拷贝的是值的引用,则为浅拷贝
console.log(jk, arr)
3.concat
let a=[1,34,{v:2}]
let b=a.concat()
b[0]=10 //值是基本类型,则为深拷贝。
b[2].v=100 //为引用类型,则拷贝的是值的引用,则为浅拷贝
console.log(b,a)
深拷贝
1.递归
let up = {
a: 1,
b: 2,
c: {
d: 20
}
}
function kaibei(up) {
let copy = {}
for (const key in up) {
if (typeof up[key] == "object") {
copy[key]= kaibei(up[key])
} else {
copy[key] = up[key]
}
}
return copy
}
let nb= kaibei(up)
nb.c.d=90
console.log( nb,up)
2.JSON
let up = {
a: 1,
b: 2,
c: {
d: 20
}
}
let io=JSON.stringify(up)
let ip=JSON.parse(io)
ip.c.d=100
console.log(ip,up)
它无法处理函数、正则表达式等特殊类型的对象。
15.var/let/const
let/const有块级作用域
function a(){
{
let bv=10。 不会提升到顶部
}
console.log(bv)//bv is not defined
}
a()
16.将多个对象合并成一个对象
const a={a:1,b:4}
const b={b:3,c:2}
// let c=Object.assign(a,b)
// let c={...a,...b}
console.log(c)
function assign(target,source){
for (const key in source) {
target[key]=source[key]
}
}
assign(a,b)
console.log(a)
Object.assign(目标对象,源对象)
Object.assign() 方法用于将一个或多个源对象的属性复制到目标对象中。它具有以下几个作用:
- 对象属性复制:Object.assign() 可以将一个或多个源对象的属性复制到目标对象中。如果目标对象中已有相同名称的属性,则源对象中的属性值会覆盖目标对象中的对应属性值。这样可以实现对象属性的合并或覆盖操作。
Object.assign(a,b,c)===a true
- 对象属性扩展:通过将一个或多个源对象复制到一个空对象中,可以实现对象属性的扩展。这对于创建新的对象并添加额外属性非常有用。
Object.assign({},b,c)
- 浅拷贝:Object.assign() 进行的是浅拷贝,即它只复制对象的引用而不是创建对象的副本。这意味着如果源对象的属性是引用类型(如对象、数组等),则目标对象和源对象的该属性将引用同一个对象。
- 合并对象:通过将多个源对象复制到一个目标对象中,可以实现对象的合并。这在需要将多个对象的属性合并到一个对象中时非常有用。
17.箭头函数
1.this
1.//箭头函数的this在定义时就确定好了的,且不能用call/apply/bind更改
let a={a:2}
let onk={
b:function(){
console.log(this)//onk
return ()=>{
console.log(this) //onk
}
// return function(){
// console.log(this) //a
// }
}
}
onk.b().call(a)
2.箭头函数不能new
3.没有prototype
4.没有arguments
18.Promise
⚠️⚠️⚠️⚠️catch两种情况
promise内部的错误不会传递到外层代码即不会被trycatch捕获,需要被自身的catch捕获
https://www.cnblogs.com/zhishiyv/p/14301982.html
onClick={async () => {
try {
const A = new Promise((resolve) => {
resolve(x + 1) //x不存在 相当于reject错误信息
})
console.log(1) 照常输出。
} catch (error) {
console.log(error)
}
}
promise中的代码 报错不会影响 外面的代码执行。状态变为reject,且不会暴漏到外面被try/catch捕获。
只能被自己的catch捕获
try {
const A = await new Promise((resolve) => {
console.log(111)
resolve(x + 1)/reject(1) 一样情况
})
console.log(A)
} catch (error) {
console.log(error)
}
为什么 给 promise加上await他内部发生的错误就会被catch捕获且 console.log(A)执行不到
但是 不写await后。内部的错误就不会被 捕获且 console.log(A)能执行到
在 JavaScript 中,使用 await 关键字可以等待一个 Promise 对象的解析结果。当使用 await 等待一个 Promise 时,如果该 Promise 被拒绝(rejected),则会抛出一个异常。这意味着在 await 表达式后面的代码不会执行,如果出现异常,可以使用 try/catch 块来捕获并处理异常。
在您提供的代码中,当您使用 await 等待一个 Promise 时,如果该 Promise 被拒绝,异常会被抛出并被 try/catch 块捕获。因此,如果在 resolve(x + 1) 中发生错误,会被捕获并在 catch 块中打印错误信息。
而当您不使用 await 等待 Promise 时,Promise 的状态变为拒绝时并不会抛出异常,因此 try/catch 块无法捕获到错误。这就解释了为什么在不使用 await 的情况下,console.log(A) 可以执行到。
<div
style={{ cursor: "pointer" }}
onClick={async () => {
try {
const A = await new Promise<void>((resolve, reject) => {
reject(1)
}).catch((err) => {
console.log(err) //1
})
console.log(A) //undefined
} catch (error) {
console.log(error) //不执行
}
}}
>
Counter
</div>
catch后下面代码还能执行
try...catch直接跳到 catch中。下面的不执行了
如果 await 表达式抛出异常,它将终止当前函数的执行,并返回一个被 reject 的 Promise 对象。您可以使用 catch 方法(await下面的代码依旧可以执行)
async function a(){
const p=await new Promise((resolve,reject)=>{
reject(11)
}).catch(()=>{
console.log(333)
})
console.log(555)
const p2=await new Promise((resolve,reject)=>{
reject(11)
})
p2.catch(()=>{ 这里不会捕获到,因为await reject后会终止代码执行,需要立即用
console.log(444)。 链式或者trycatch才行
})
console.log(666)
}
a()
或 try...catch (直接到了catch中)语句来处理这个 reject 的 Promise。
then( )方法指定的回调函数,如果运行中抛出错误,也会被catch( )方法捕获
1.Promise构造函数new的对象
let pro=new Promise((res,rej)=>{ //回调函数为执行器
res(12)
})
console.log(pro) //Promise {<fulfilled>: 12}
// pro.then(()=>{
// console.log(111)
// },()=>{
// console.log(222)
// })
// pro.catch(()=>{
// console.log(222)
// console.log(pro)
// })
// pro.then((res)=>{
// console.log(res)
// }).catch((err)=>{
// console.log(222)
// })
2.Promise构造函数的静态方法
1.参数不是一个Promise对象
Promise.resolve(12) //将12变为Promise对象 //Promise {<fulfilled>: 12}
相当于
new Promise((res)=>{res(12)})
2.参数是一个Promise对象
console.log(Promise.resolve(new Promise((res,rej)=>{ //传入什么就返回什么。不改变
rej(12)
}))) //Promise {<rejected>: 12}
Promise.reject(12) //将12变为Promise对象 //Promise {<rejected>: 12}
相当于
new Promise((res,rej)=>{rej(12)})
Promise.resolve(null)
.then((res)=>{
if(res){
console.log(res)
}else{
return Promise.reject(null)
}
}).catch((err)=>{
console.log(err) //null. //为什么状态变为fulfilled后还能在去执行catch
//看下面的链式调用
})
接受一个参数作为拒绝原因(reason),可以是任何数据类型。
返回一个以给定原因被拒绝的 Promise 对象。
如果参数是一个 Promise 实例,则返回的 Promise 对象将直接拒绝,并且reason是传递的这个promise。
const C = Promise.reject(Promise.resolve(5)) 。 传入一个promise对象
如果成功返回的是promise实例。实例的值是一个数组
console.log(
Promise.all([
new Promise((resolve, reject) => {
resolve(12)
}),
new Promise((resolve, reject) => {
resolve(13)
}),
])
)
如果失败返回的是promise实例。实例的值是失败的值
需要用then或catch去接受值
Promise.all([
new Promise((resolve, reject) => {
resolve(12)
}),
new Promise((resolve, reject) => {
resolve(13)
}),
new Promise((resolve, reject) => {
reject(14)
}),
]).then((res)=>{
console.log(res)
}).catch((err)=>{
console.log(err)
})
如果传入的是一个基本类型的数组。则相当于promise.resolve(...)
Promise.all([1,{a:1}])
.then((res)=>{console.log(res)})-->[1,{a:1}]
async function a(){
Promise.allSettled([new Promise((resolve, reject) => {
reject(11)
}),Promise.resolve(12)])
.then((res)=>console.log(res)) 数组
.catch((e)=>{console.log(e)})
}
a()
3..Promise的链式调用
Promise.resolve(new Promise((res,rej)=>{ //参数是一个Promise对象,传入什么就返回什么。不改变
rej(12)
}))
.then((res)=>{
console.log(res)
})
.catch((err)=>{
console.log(err) //12
})
.then/.catch中的return目测都经过了 Promise.resolve(XXXX)的加工,返回的一直是一个新的Promise对象,因为这个对象的原型链上有then/catch方法,所以能链式调用
- 如果传入的是Promise对象,则原样返回这个Promise对象
- 如果传入的不是Promise对象,则经过了 Promise.resolve(XXXX)的加工
- 如果没写return,则经过了 Promise.resolve(undefined)的加工
let p1=new Promise((res,rej)=>{
res(12)
})
console.log(p1) //Promise {<fulfilled>: 12}
p1
.then((res)=>{
console.log(res) //12
//如果不写return,则默认return undefined
//且状态为fulfilled
//相当于return Promise.resolve(undefined)
})
.then((res)=>{
console.log(res) //undefined
})
let p1=new Promise((res,rej)=>{
res()
})
p1
.then((res)=>{
return [] //写了return且返回的不是Promise对象 ,则相当于return Promise.resolve([])
})
.then((res)=>{
console.log(res)//[]
})
p1
.then(123,456)如果不是一个函数。生成的新promise对象的值还是之前的promise的
let po=new Promise((res,rej)=>{
res(null) //Promise {<fulfilled>: null}
})
console.log(po)
po
.then((res)=>{
return Promise.reject(10) //直接到catch
})
.then((res)=>{
return Promise.reject(12) //跳过
})
.catch((err)=>{
console.log(err) //10
return Promise.resolve(13)
})
.then((res)=>{
console.log(res) //13
})
let po=new Promise((res,rej)=>{
res(null) //Promise {<fulfilled>: null}
})
console.log(
po
.then((res)=>{
return 10
})
.then((res)=>{
return Promise.resolve(12)
})
)
打印结果为
Promise {<pending>}
[[Prototype]]: Promise
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: 12
19.async await
解决Promise链式调用问题
async函数
async 函数中的return和链式调用的返回规则相同
async function a(){
return 12
}
console.log(a()) //Promise {<fulfilled>: 12}
async function a(){
return new Promise((resolve, reject) => {
reject(13)
})
}
a().then((res)=>{
console.log(res)
}).catch((err)=>{
console.log(err) //13
})
await处理不同的数据(普通/resolve/reject)
async function A(){
// let res = await 12
// console.log(res) //12 //普通的就返回普通的
// let res=await new Promise((resolve, reject) => {
// resolve(13)
// })
// console.log(res)//13 //resolve的就返回 resolve(13)括号中的
let res=await new Promise((resolve, reject) => {
reject(14)
})
console.log(res)//Uncaught (in promise) //14 //reject的会报错,可以将它返回然后用catch处理
return res
}
A().
then((res)=>{
console.log(res)
}).
catch((err)=>{
console.log(err) //14
})
await阻塞
await 关键字会暂停当前函数的执行,直到 Promise 的状态变为 resolved 或 rejected。
async function A(){
let res=await new Promise((resolve, reject) => {
setTimeout(() => {
resolve(14) 不resolve不会往下走 await+promise
console.log(2222) //第一个
}, 1000);
})
console.log(res) //第二个
return res
}
A().
then((res)=>{
console.log(res) //第三个
}).
catch((err)=>{
console.log(err)
})
await new Promise((resolve) => {
// resolve(x + 1)
}).catch((err) => {
console.log(err)
})
console.log(1111)
不resolve不会往下走 await+promise
不加await.不写resolve则没事
所有微任务 执行完才会去执行宏任务。即使微任务里面还 嵌套微任务,也会执行完再去执行别的
function testSometing() {
console.log("执行testSometing");
setTimeout(()=>{
console.log(111111)
setTimeout(()=>{
console.log(3333)
})
})
return "testSometing";
}
async function testAsync() {
console.log("执行testAsync");
return Promise.resolve("hello async");
}
async function test() {
console.log("test start...");
//await后面跟着的代码照常执行
const v1 = await testSometing();//关键点1
//await将它下面的代码全部算作微任务
console.log(v1);
const v2 = await testAsync();
//开启另一个微任务队列
console.log(v2);
console.log(v1, v2);
var promise = new Promise((resolve) => {
resolve("promise22");
});//关键点2
promise.then((val) => console.log(val));
setTimeout(()=>{
console.log(22222)
})
}
test();
var promise = new Promise((resolve) => {
console.log("promise start..");
resolve("promise");
});//关键点2
promise.then((val) => console.log(val));
console.log("test end...")
async function sleep(){
await new Promise((resolve, reject) => {
setTimeout(()=>{
console.log(2222)
},1000)
resolve(1)//状态不变不会往下走
})
console.log(11111)
}
sleep()
20.节流/防抖( 运用到闭包)
//防抖
//多次触发同一事件,只执行最后一次
//输入框
const input = document.querySelector('input')
input.addEventListener('input',debounce(
function() {
console.log(111);
},
1000))
function debounce(fn,time){
let timer=null;
return function(){
clearTimeout(timer);
timer=setTimeout(()=>{
fn()
},time)
}
}
一段时间内只执行一次,时间不到触发再多次也不起作用
王者荣耀技能冷却
function debounce(fn,time){
return function(){
if(fn.timer) return;
fn.timer=setTimeout(()=>{
fn();
fn.timer=null
},time)
}
}
改进
闭包
function am(fn, time) {
//防抖
// return function () {
// if(fn.timer) clearTimeout(fn.timer)
// fn.timer = setTimeout(fn, time)
// }
//节流
return function(){
if(fn.timer)return
fn.timer = setTimeout(()=>{
fn()
fn.timer=null
}, time)
}
}
const input = document.querySelector('input')
input.addEventListener('input',am(
function() {
console.log(111);
},
1000))
21.手写instanceof
function instance(left,right){
let L=left.__proto__;
let R=right.prototype;
while(true){
if(L===null)return false
if(L===R) return true;
L=L.__proto__
}
}
console.log(instance([],Object))
22.全局通用的数据类型判断方法
function getType(obj){
let type = typeof obj;
if (type !== "object") { // 先进行typeof判断,如果是基础数据类型,直接返回
return type;
}
// 对于typeof返回结果是object的,再进行如下的判断,正则返回结果
return Object.prototype.toString.call(obj).replace(/^\[object (\S+)\]$/, '$1'); // 注意正则中间有个空格
}
23.类数组转化为数组的方法
function ar() {
console.log(
// Array.from(arguments)
// [...arguments]
// Array.prototype.concat.apply([],arguments)
// Array.prototype.slice.call(arguments)
)
}
ar(1, 2, 3)
24.原型链继承
function Fat(name){
this.name=name
}
function Chi(){
Fat.call(this,20)// 可以给父类传入参数同时将父类的属性归为自己的
//不写这条child也有name属性,但是不是自己的,是原型链上的
}
Chi.prototype=new Fat()
let child1=new Chi()
console.log(child1.name)
new Chi()只是将新生成的对象的__proto__===Chi函数的prototype,并咩有指明是谁
Chi.prototype=new Fat() 这里才指明是谁
才构成了原型链
25.数组扁平化
toString() 展开深度无限
var arr=[1,2,3,[[4,5],6],7,8,9]
console.log(arr.toString() ----》“1,2,3,4,5,6,7,8,9”
.split(',')) ----》['1','2','3','4','5','6','7','8','9']
递归
function deep(arr){
let a=[]
for (const key in arr) {
if(Array.isArray(arr[key])){
a=a.concat( deep(arr[key]))
}else{
a.push(arr[key])
}
}
return a
}
console.log( deep([1,[2,34,10],8]))
flat
[1,[2,34,10],8].flat()
//数组的展开深度(默认不填、数值是 1),即展开一层数组。如果层数不确定,参数可以传进 Infinity,代表不论多少层都要展开:
26.交换a,b的值,不能用临时变量
let a=20
let b=90
a=a+b
b=a-b
a=a-b
b=[a,b]
a=b[1]
b=b[0]
27.请实现一个 add 函数,满足以下功能
add(1); // 1
add(1)(2); // 3
add(1)(2)(3);// 6
add(1)(2, 3); // 6
add(1, 2)(3); // 6
add(1, 2, 3); // 6
args闭包
function add(...args) {
// 在内部声明一个函数,利用闭包的特性保存并收集所有的参数值
let fn = function(...newArgs) {
return add.apply(null, args.concat(newArgs))
}
// 利用toString隐式转换的特性,当最后执行时隐式转换,并计算最终的值返回
fn.toString = function() {
return args.reduce((total,curr)=> total + curr)
}
return fn
}
// 测试,调用toString方法触发求值
add(1).toString(); // 1
add(1)(2).toString(); // 3
add(1)(2)(3).toString();// 6
add(1)(2, 3).toString(); // 6
add(1, 2)(3).toString(); // 6
add(1, 2, 3).toString(); // 6
28.手写双向数据绑定
let obj = {}
let input = document.getElementById('input')
let box = document.getElementById('box')
// 数据劫持
Object.defineProperty(obj, 'text', {
get() {
console.log('获取数据了')
},
set(newVal) {
console.log('数据更新了')
input.value = newVal
box.innerHTML = newVal
}
})
// 输入监听
input.addEventListener('keyup', function (e) {
obj.text = e.target.value
})
29.手写Promise
1.基础
class pro {
constructor(exFn) {
this.status = 'pending',
this.value = undefined;
this.reason = undefined;
let resolve = (value) => {
if (this.status === 'pending') {
this.status = 'fulfilled'
this.value = value
}
}
let reject = (value) => {
if (this.status === 'pending') {
this.status = 'rejected'
this.reason = value
}
}
exFn(resolve, reject) //难点
}
then(onFulfilled, onRejected) {
if (this.status === 'fulfilled') {
onFulfilled(this.value)
} else {
onRejected(this.reason)
}
}
}
new pro((res, rej) => {
res(12)
})
.then((value) => {
console.log(value)
}, (reason) => {
console.log(reason)
})
2.进阶
class ExPromise {
constructor(executor){
this.status='pending'
this.value=undefined;
//为链式调用准备,且执行到then时promise的状态还未改变需要存起来
this.resolveCallBack=[];
this.rejectCallBack=[];
const resolve=(value)=>{
//只有状态为pending的promise才能改变状态
if(this.status==='pending'){
this.status='fulfilled';
this.value=value;
this.resolveCallBack.forEach((callback)=>{
callback()
})
}
}
const reject=(reason)=>{
//只有状态为pending的promise才能改变状态
if(this.status==='pending'){
this.status='rejected';
this.value=reason;
this.rejectCallBack.forEach((callback)=>{
callback()
})
}
}
try {
//内部报错不影响外部,直接reject
executor(resolve,reject)
} catch (error) {
reject(error)
}
}
//传入两个参数
then(resolveFn,rejectFn){
//返回一个新的promise对象
return new ExPromise((resolve,reject)=>{
const resolveCallback=()=>{
//这里的this是调用then的promise,箭头函数向外遇到箭头函数再向外
try {
if(typeof resolveFn ==='function'){
const result= resolveFn(this.value)
resolve(result)
}else{
//如果不是一个函数,则生成的新promise对象的值还是之前的promise的
resolve(this.value)
};
} catch (error) {
reject(error)
}
}
const rejectCallback=()=>{
try {
if(typeof rejectFn ==='function'){
const result= rejectFn(this.value)
resolve(result)
}else{
//如果不是一个函数,则生成的新promise对象的值还是之前的promise的
reject(this.value)
};
} catch (error) {
reject(error)
}
}
//这里的this是之前的promsie的
if(this.status==='fulfilled'){
//then中的代码为异步
setTimeout(resolveCallback,0)
}
else if(this.status==='rejected'){
//then中的代码为异步
setTimeout(rejectCallback,0)
}
else{
//当之前的promsie的状态为pending时
this.resolveCallBack.push(resolveCallback)
this.resolveCallBack.push(rejectCallback)
//为什么会有这种情况且需要是一个数组
// const a=new ExPromise((resolve, reject) => {
// setTimeout(()=>{ resolve(12)},3000)
// })
//此时,执行到这里时,a的状态为pending,
//当之前的promise状态改变时,会执行resolveCallBack数组
// a.then((value)=>console.log(value))
//这时就需要一个数组
// a.then((value)=>console.log(value+1))
}
})
}
}
const a=new ExPromise((resolve, reject) => {
resolve(12)
})
console.log(a)
console.log(a.then(
(res)=>{
console.log(res+b)
// return 13
},
11))
3.终极(增加catch/静态resolve、reject 、all、 race)
class ExPromise {
constructor(executor){
this.status='pending'
this.value=undefined;
//为链式调用准备,且执行到then时promise的状态还未改变需要存起来
this.resolveCallBack=[];
this.rejectCallBack=[];
const resolve=(value)=>{
//只有状态为pending的promise才能改变状态
if(this.status==='pending'){
this.status='fulfilled';
this.value=value;
this.resolveCallBack.forEach((callback)=>{
callback()
})
}
}
const reject=(reason)=>{
//只有状态为pending的promise才能改变状态
if(this.status==='pending'){
this.status='rejected';
this.value=reason;
this.rejectCallBack.forEach((callback)=>{
callback()
})
}
}
try {
//内部报错不影响外部,直接reject
executor(resolve,reject)
} catch (error) {
reject(error)
}
}
//传入两个参数
then(resolveFn,rejectFn){
//返回一个新的promise对象
return new ExPromise((resolve,reject)=>{
const resolveCallback=()=>{
//这里的this是调用then的promise,箭头函数向外遇到箭头函数再向外
try {
if(typeof resolveFn ==='function'){
console.log(this,this.value)
const result= resolveFn(this.value)
resolve(result)
}else{
//如果不是一个函数,则生成的新promise对象的值还是之前的promise的
resolve(this.value)
};
} catch (error) {
reject(error)
}
}
const rejectCallback=()=>{
try {
if(typeof rejectFn ==='function'){
const result= rejectFn(this.value)
resolve(result)
}else{
//如果不是一个函数,则生成的新promise对象的值还是之前的promise的
reject(this.value)
};
} catch (error) {
reject(error)
}
}
//这里的this调用then的promsie的
if(this.status==='fulfilled'){
//then中的代码为异步
setTimeout(resolveCallback,0)
}
else if(this.status==='rejected'){
//then中的代码为异步
setTimeout(rejectCallback,0)
}
else{
//当之前的promsie的状态为pending时
this.resolveCallBack.push(resolveCallback)
this.resolveCallBack.push(rejectCallback)
//为什么会有这种情况且需要是一个数组
// const a=new ExPromise((resolve, reject) => {
// setTimeout(()=>{ resolve(12)},3000)
// })
//此时,执行到这里时,a的状态为pending,
//当之前的promise状态改变时,会执行resolveCallBack数组
// a.then((value)=>console.log(value))
//这时就需要一个数组
// a.then((value)=>console.log(value+1))
}
})
}
catch(rejectFn){
return this.then('',rejectFn)
}
static resolve(value){
return new ExPromise((resolve,reject)=>{
resolve(value)
})
}
static reject(reason){
return new ExPromise((resolve,reject)=>{
reject(reason)
})
}
static all(arr){
let result=[];
let count=0;
return new ExPromise((resolve,reject)=>{
for (const promise of arr) {
promise.then((value)=>{
result.push(value)
if(arr.length===result.length){
resolve(result)
}
})
.catch((reason)=>{
//第一个为rejected的promise的值
reject(reason)
})
}
})
}
static race(arr){
return new ExPromise((resolve,reject)=>{
for (const promise of arr) {
promise
//.then((value)=>resolve(value))
//下面是终极用法
//这个resolve是现在新promsie的。是一个函数
//传递进去执行 const result= resolveFn(this.value)去改变了状态,目的就达到了
.then(resolve)
.catch(reject)
}
})
}
}
30.手写map
let arrp = [1, 2, 35, 8]
Array.prototype.newMap = function (fnCallback) {
if (Object.prototype.toString.call(fnCallback) !== '[object Function]') return '必须传一个函数';
let arr = [];
for (let index = 0; index < this.length; index++) { //不能用for in .会将newMap方法也给算进去(可枚举)
arr.push(fnCallback(this[index], index, this))
}
return arr
}
let a = arrp.newMap((item, index, arrp) => {
return item += 1
})
console.log(a)
31.localStorage
1.状态共享
你确定多窗口之间sessionStorage不能共享状态吗?_localstorage在所有窗口共享_A 风的博客-CSDN博客
那同源下多窗口间localStorage能共享吗?
同一浏览器的相同域名和端口的不同页面间可以共享相同的 localStorage
但是在A页面的控制台localStorage.setItem('p','h'),B页面也会改变的。
那sessionStorage在同源多窗口之间能共享状态吗?
同一浏览器的相同域名和端口的不同页面间可以共享相同的 sessionStorage
前提条件是B页面是从A页面window.open的,新开的页面会复制之前页面的sessionStorage!!,(包括控制台新设置的)(地址栏直接输入,新页面只会有代码设置的,不会有控制台设置的)
但是在A页面的控制台sessionStorage.setItem('p','h'),B页面不会改变的。只会复制打开之前的
2.生命周期
localStorage
在A页面的控制台localStorage.setItem('p','h'),B页面也会改变的。且关闭页面再重新都打开后。新设置的这个还在
sessionStorage
但是在A页面的控制台sessionStorage.setItem('p','h'),B页面不会改变的。且关闭页面再重新都打开后,新设置的这个不在了,随着会话结束销毁了
32.break\continue\return
1、break:执行break操作,跳出所在的当前整个循环,到外层代码继续执行。
2、continue:执行continue操作,跳出本次循环,从下一个迭代继续运行循环,内层循环执行完毕,外层代码继续运行。
3、return:执行return操作,直接返回函数,所有该函数体内的代码(包括循环体)都不会再执行。同时结束其所在的循环和其外层循环。
33.封装localStorage的设置和获取
var custom_localStorage = {
set : function(key, value){
var item = {
data : value
}
//序列化对象之后变成字符串再来存储
localStorage.setItem(key, JSON.stringify(item));
}
get : function(key){
var val = localStorage.getItem(key);
if(!val) return null;
return JSON.parse(val);//把序列化字符串解析成对象返回
}
}
var __localStorage = {
//添加缓存时间,缓存时间以天计算
set : function(key, value, days){
//判断是否传递参数days
if(typeof(days) == 'undefined'){
var item = {
data : value
};
}else{
var item = {
data : value,
endTime : new Date() . getTime() + days * 24 * 60 * 60 * 1000
};
}
localStorage.setItem(key, JSON.stringify(item));
}
get : function(key){
var val = localStorage.getItem(key);
if(!val) return null;
val = JSON.parse(val);
//判断是否设置过期时间
if(typeof(val.endTime) == 'undefined' || (typeof(val.endTime) != 'undefined' && val.endTime > new Date().getTime())){
return val.data;
}else{
localStorage.removeItem(key);
return null;
}
}
}
2.工作中的总结
1.字符串方法
1.Object.entries
Object.entries({a:1,b:2,c:3})---->[[a,1],[b,2],[c,3]]
2.localeCompare
var str = 'aaa',
strCom = 'bbb',
strCom2 = 'aaa';
str.localeCompare(strCom); //-1
strCom.localeCompare(str); //1
str.localeCompare(strCom2); //0
- a代表相邻两元素中后面一个,b代表前面一个!!!
所以a-b>0是升序
ar strList = ['cc', 'ee', 'ca', 'aa'];
strList.sort((a, b) => {
return a.localeCompare(b);
});
console.log(strList); //["aa", "ca", "cc", "ee"]
3.substring,slice
name.substring(0, name.lastIndexOf("."))。 获取文件名
substring,slice相同。截取。原来的保持不变,返回被截取的部分,同数组
4.startsWith
startsWith() 方法用于检测字符串是否以指定的子字符串开始。
'abcd'.startsWith('a')--->true
2.URL.createObjectURL(file) 图片可用
<input type="file"name="file" id="file"
onChange={() => {
console.log(document.querySelector('#file')?.files[0]);//file文件对象
console.log(window.URL.createObjectURL(document.querySelector('#file')?.files[0]));//base64URl
}}
/>
3.new URL()
const urlParams= new URL(window.location.href).searchParams.get('appKey')//999999
const hashUrl = window.location.hash.slice(1); // 去除 # 号
const match = hashUrl.match(/appKey=(\d+)/);
4.解构赋值
const {
message,
message:{type}, 嵌套解构
card='', 默认值
kid:cida, 重命名
...values 剩余值
}=dialog