文章目录
基础语法
js中!和!!的区别及用法
ECMAScript 6 入门
js中==和===区别
对象
JavaScript 对象
js继承之原型继承
每创建一个函数,该函数就会自动带有一个 prototype 属性,该属性是个指针,指向了一个对象,我们称之为 原型对象。原型对象上默认有一个属性 constructor,该属性也是一个指针,指向其相关联的构造函数。通过调用构造函数产生的实例,都有一个内部属性,指向了原型对象。
解释一下上面的话,下图中,我们定义了一个Test
方法,那么Test
的类型是一个function
,可以称这个Test
为构造函数,这个函数本身有一个属性叫做prototype
,其指向的对象,就是原型对象,从中可以看出这个原型对象里有两个属性,分别是constructor
和__proto__
然后通过调用new Test()
构造实例,可以看到这个实例对象里有一个__proto__
的属性,指向的就是Test
的原型对象
JS中PROTOTYPE属性解释及常用方法
这篇文章更加深入一点
1.3里解释了为什么向构造函数的prototype
里添加属性会影响后续构建的所有对象,因为当一个对象被创建时,这个构造函数 将会把它的属性prototype赋给新对象的内部属性__proto__。这个__proto__被这个对象用来查找它的属性。 从上面的例子就可以看出
1.6里说:这个区别就是function定义的方法(对象方法)有一个prototype属性,使用new生成的对象就没有这个prototype属性。也就是prototype属性是对象方法或者构造方法的专有属性。 我没懂,可能意思是说(以上面为例),Test
里有prototype
属性,而let t=new Test()
的t
里只有一个__proto__
没有prototype
吧
刚接触通过函数构建对象(对象方法),我的疑惑是,为啥function Test(val){this.val=val}
就可以用来构建对象了,例如let t=new Test("1")
,后来一想,这可能就是this
的功劳,在上面这篇博客里的**构造函数版this:**的讲解可以看到
JavaScript中call,apply,bind方法的总结。
遍历
变量作用域
JavaScript 变量提升
函数的作用域是什么?js 的作用域有几种?
函数作用域和块作用域
理解es6中的暂时性死区
当程序的控制流程在新的作用域(module, function或block作用域)进行实例化时,在此作用域中的用let/const声明的变量会先在作用域中被创建出来,但因此时还未进行词法绑定,也就是对声明语句进行求值运算,所以是不能被访问的,访问就会抛出错误。
前端面试题:JS中的let和var的区别
前面提到了var使用的是函数作用域以及变量提升,那个f
函数里打印出来的第一个undifine
就是变量提升的结果,在f
函数里的作用域,程序构建了一个新的函数作用域,然后发现这个作用域里有一个var
定义了变量,与let
类似,这个var
的变量就会被绑定到这个作用域里并且其声明会被提升到前面去,但此时没有进行词法绑定(求值运算或赋值),但var
仍可以被访问。
f
里的变量只会影响这个函数作用域的,影响不到f
外面
数据类型
异步
JavaScript异步机制详解
JavaScript:彻底理解同步、异步和事件循环(Event Loop)
“事件”就是线程之间相互通知的信息载体,通过一个event对象来记录
Promise
如何使用 Promise
Promise
怎么理解new Promise(function (resolve, reject){})中函数的参数resolve和reject
function test(resolve, reject) {
if (Math.random() * 2< 1) {
console.log('call resolve()...');
resolve('200 OK');
console.log('call resolve() done');
} else {
console.log('call reject()...');
reject('less then 0.5');
}}
var p1 = new Promise(test);
console.log('1');
var p2 = p1.then(result=>console.log('成功:' + result));
var p3 = p2.catch(reason=>console.log('失败:' + reason));
可以先简单了解promise的用法,
test
方法是一个耗时函数,而p1
是个Promise
对象,就是test
方法的容器,这个容器会让test
方法(以事件循环的方式)异步执行也就是说创建了一个异步任务。并根据执行过程中的结果来判断是执行resolve
方法还是reject
方法,以上面为例,如果随机数大于0.5,就执行resolve
方法,如果小于0.5,就执行reject
方法,而这两个方法具体是什么呢?就是由下面那个then
和catch
来指定的。
但需要特别说明的是,then里传递进的那个匿名函数,并不完全等同于resolve
方法,catch也同理。为什么这么说,因为在p1
对象生成的时候,test
方法就有可能已经被执行了,而这时候并没有指定then
方法,所以resovle
方法具体做了什么,肯定程序肯定是不知道的;而且从上面代码中可以看出,即使没有指定resolve
方法具体要做什么,resolve
方法仍旧被执行了,那这只有一个可能,resolve
方法本身的执行也是异步的,反正就是要执行一个方法,具体要做什么事情,什么时候then调用了,什么时候执行。但是then
传入的方法不能等同视为resolve
方法,因为如果then方法返回了一个新的promise,then方法还支持链式调用,所以若果将then
传入的方法不能等同视为resolve
方法,那么then
的返回值我们是拿不到的了。我怀疑用了什么手段,让resolve
或者reject
方法被调用时,也是异步执行的,如果调用了resolve
方法,那么就从then
里找到这个具体实现,并且将执行的结果取出来用于链式调用或者结束。
使用 Promise
可以详细了解Promise
对象到底是什么,本质上 Promise 是一个函数返回的对象,我们可以在它上面绑定回调函数,这样我们就不需要在一开始把回调函数作为参数传入这个函数了。并且
可以通过 Promise 的构造器从零开始创建 Promise。这种方式(通过构造器的方式)应当只在封装旧 API 的时候用到。理想状态下,所有的异步函数都已经返回 Promise 了。
也就是说,一般情况下,我们不会手动new一个Promise对象,除非是setTimeout(fn)
这类的旧API,这个方法是在一点时间后,将fn放入事件循环队列里等待执行。
这个api是通过将方法fn
作为参数传进setTimeout
,实现将fn
注册为回调函数。但是如果fn
执行失败,外部没办法捕获这个异常,异常会被抛到最上层,这不是我们希望的,这是因为setTimeout
没提供这个捕获的功能。
function saySomething(s){
throw new Error('有哪里不对了');
console.log("say somting"+s);
}
setTimeout(() => saySomething("10 seconds passed"), 1000);
通过如下方法可以将异常捕获掉,原理是,promise的异步方法其实就仅仅是执行一个setTimeout
,而resolve方法本身又是promise的回调,虽然其执行的时机会延后个10s,但无论怎样,当执行过程中出现异常的时候,会被catch方法接住
const failureCallback = err=>console.log(err);
const wait = ms => new Promise(resolve => setTimeout(resolve, ms));
wait(1000).then(() => saySomething("10 seconds")).catch(failureCallback);
If the function passed as handler to then returns a Promise, an equivalent Promise will be exposed to the subsequent then in the method chain.
如下所示,如果then方法本身又返回了一个promise,这个新的promise会暴露出来,也就是说,promise3可以视作等同于promise2,但两者并不是同一个对象,可以通过Object.is(promise2,promise3)
返回false来判断。
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('promise1');
}, 300);
});
const promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('promise2');
}, 300);
});
const promise3 = promise1.then((v) => {
console.log(v);
return promise2;
});
promise3.then(
(v) => {
console.log(v);
}
);
In brief, Promise.resolve() returns a promise whose eventual state depends on another promise, thenable object, or other value.
例如,Promise.resolve(123)
生成的对象就依赖于123,再比如Promise.resolve(anotherPromise)
生成的对象就依赖于anotherPromise
Let’s talk about how to talk about promises
主要描述了什么是resolved
,doStuff()的promise就是resolved,他is resolved to second方法返回的promise。所以说,
已决议(resolved),它表示 promise 已经处于已敲定状态,或者为了匹配另一个 promise 的状态被“锁定”了
function doStuff() {
return first().then(firstResult => return second(firstResult));
}
async/await
理解 JavaScript 的 async/await
补充说明,如果then
方法里又返回了一个新的Promise
对象,那么这个后续的then
方法也需要等这个Promise
被resolve
之后才能执行,我怀疑如果then
里直接返回的是一个非Promise
对象,例如“123”,then
本身也会返回Promize.resolve(123)
对象。
那么什么是async
?没有async
的时候,创建一个异步任务,是将耗时方法作为参数传入 Promise
构造函数完成的。而有了async/await
,可以直接将原本用在Promise
构造函数里的耗时方法丢在async function
里,并调用这个async function
来返回Promise
对象;好处就是多个万一耗时操作由多个连续的操作组成怎么办。
而使用Promise
注册回调的时候,如果需要级联多个耗时操作的回调(而这往往是因为每个回调又都是一个返回Promise
的方法),这时就需要反复地then
。而有了async/await
,可以通过在async function
内部使用await+try catch
语法改写上述每个返回Promise
的回调,从而实现像写同步方法那样写异步方法。见《async/await 的优势在于处理 then 链》
如果它等到的是一个 Promise 对象,await 就忙起来了,它会阻塞后面的代码,等着 Promise 对象 resolve,然后得到 resolve 的值,作为 await 表达式的运算结果。
下列例子里,v就是resolve
方法的入参,所以,await的是resolve方法的执行
function takeLongTime() {
return new Promise(resolve => {
setTimeout(() => resolve("long_time_value"), 1000);
});
}
async function test() {
const v = await takeLongTime();
console.log(v);
}
test();
Generator 函数的含义与用法
async 函数的含义和用法
模块
JS 之export、export default和module.exports
js中的export、import、export default等导入导出全解
导包时import花括号{}的作用
运算符
The left-hand side of an assignment expression may not be an optional property access.ts(2779)
?.
是可选链运算符号,只能用于给读取值或者别人赋值,像这种ref.current?.style.height='100px'
属于被赋值
BOM与DOM
screen
screen描述的是整个屏幕
window
window描述的是整个浏览器窗口的信息
navigator
navigator描述的是浏览器自身所带的信息(例如版本等)
location
节点树
获取节点的属性和子节点<div id="d1" class="div"> <div id="d2" class="div">text</div> </div>
获取属性和属性的值,但建议还是用nodeName和nodeValue,不容易混淆,因为文档中都是节点
元素节点:<div class="class" id="d1">123</div>
就是元素节点,获取方式为document.getElementById("id")
属性节点:<div> class="class" id="d1">123</div>
里的class="class"
就是元素节点,获取方式为document.getElementById("id").attributes['class']
或者.attributes[0]
内容节点 <div> class="class" id="d1">123</div>
里的123
就是内容节点,获取方式为document.getElementById("id").childNodes[0]
需要进一步说明的是,没有明确指定的属性是不会生成属性节点的,例如<input />
这个元素节点的属性节点是空的,所以要访问其value
,只能通过.value
的方法;另外,通过.
可以获得元素节点的全部非自定义属性的运行时使用的值
例如<input type="checkbox" checked="checked"/>
,.checked
返回结果为true
,并且其结果会随着选中状态的改变而改变;而.attributes['checked']
返回结果为checked
,即只能获取html页面(浏览器调试台的element页面)上写的属性的值,当然如果使用了js修改了某个属性(例如name)的值,.attributes['checked']
也会跟着改变。
不要用自身的子节点替换另一个子节点,如下所示,本意是将第一个节点替换为第二个节点,但这样的操作会直接导致第一个节点被删除,结果变为2 3
;而如果将outerD.replaceChild(children[1],children[0]);
改为outerD.replaceChild(children[0],children[1]);
,结果会导致第二个节点被删除,结果变为1 3
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script>
function f() {
let outerD=document.getElementById("outerD");
let children=outerD.children;
outerD.replaceChild(children[1],children[0]);
}
</script>
</head>
<body>
<div id="outerD">
<div id="d1">1</div>
<div id="d2">2</div>
<div id="d3">3</div>
</div>
<button onclick="f()">button</button>
</body>
</html>
为什么呢?个人理解是这样的。对于节点的操作有appendChild
,insertBefore
,replace
等等很多,以replace(newNode,oldNode)
为例,功能是将oldNode
节点替换为newNode
节点,具体操作过程是分步的:即首先将newNode
节点从其原本所属的父节点上拆下来,然后把oldNode
替换为newNode
;如上例,outerD.replaceChild(children[1],children[0]);
,其中children[1]
为d2
,children[0]
为d1
,先把d2
从outerD
上拆下来,此时outerD
上只有d1
和d3
,随后将d1
替换成d2
,此时就剩d2
和d3
了。appendChild
同理,需要将待添加的元素从原来的父节点上拆下来,加到新的节点后面
JavaScript中querySelector()和getElementById()(getXXXByXX)的区别
由于querySelector返回的是静态的,因此在使用template的时候,使用的是query获取template节点,以免破坏template本身
事件
JS事件机制
简述 JavaScript 的事件捕获和事件冒泡
冒泡与捕获阶段
下面这个键盘敲击事件传入的是this
,也就是元素节点对象,你会发现当你输入的是abc
的时候,事件函数获取其value
为ab
,也就是说,onkeypress
事件函数会发生在input
标签的value
更新之前;为了修正这个问题,我们可以把onkeypress
改为onkeyup
,onkeyup
的事件函数会发生在input
标签的value
更新之后,这也就是说,输入框的更新时机是键盘敲击之后,抬起之前
<input id="username" type="text" onkeypress="check_name(this)"/>
function check_name(element){console.log(element.value)}
事件参考
目前的html规范已经不建议在html标签里直接写onclick
的属性了,而是建议按dom2的方式书写,但是我觉得这样有点麻烦啊= =,例如,要在页面加载完成后绑定事件方法,以及用onkeydown="return false"
可以禁止输入,但使用下面的方法如何控制禁止输入呢
<input id="username" type="text"/>
function check_name(event) {
console.log(event);//事件本身
console.log(this);//事件触发的元素节点
}
document.getElementById("username").addEventListener("keypress",check_name);
如何禁止输入,找到了一个答案
function check_name(event) {
console.log(event);//事件本身
console.log(this);//事件触发的元素节点
event.returnValue=false;//通过这个属性进行更改
}
或者dom0方式进行时间绑定
document.getElementById("someID").onkeydown=function(event){
console.log(this);//事件触发的元素节点
console.log(event);//事件本身
}
document.getElementById("dom0").onkeydown=null;//注销事件
表单事件onsubmit
可以阻止表单的提交,如onsubmit="return f()"
且f(){return false}
,onkepress
也可以实现禁止输入
jQuery
选择器
<div id="d1">Hello JQuery</div>
<div >Hello JQuery</div>
可以看到返回的jQuery对象有一个叫0
的属性,这个属性的对象就是div#d1
这个div
,可以通过$("#d1")[0]
来获取这个对象,这个对象就是DOM原生的元素节点对象
jQuery里的this
<script>
$(function(){
$(".b").click(function(){
console.log(this,this.value);//this就是选择器所选择的标签,是一个DOM原生的元素节点对象,
let jQuery_obj = $(this);//用$将元素节点对象转化为jQuery对象
console.log(jQuery_obj.val());//使用jQuery对象的方法
});
});
</script>
<button value=":input" class="b">切换所有的:input</button>
prop
和attr
的关系类似于从原生DOM的元素节点上通过.
和通过属性节点获得属性的值。prop
能获取元素节点的全部非自定义属性的运行时使用的值****,而attr
只能获取html页面**(浏览器调试台的element页面)上写的属性的值,例如
<input type="checkbox" id="c" game="LOL" checked="checked" name="check"/>
通过$("#c").prop("checked")
的结果true
,其结果会随着选中状态的改变而改变,而$("#c").attr("checked")
的结果checked
,选中状态的改变并不会改变checked
这个属性的值,所以.attr("checked")
不会跟着变,当然如果使用了js修改了某个属性(例如name)的值,.attr("name")
也会跟着改变。
事件
jQuery的事件对象不是DOM原生的事件对象,是对其的封装,不过功能基本相同
Nodejs
何为nodejs-和-npm
nodejs就是一个js的运行环境
nvm
nvm node_mirror https://npmmirror.com/mirrors/node/
nvm npm_mirror https://npmmirror.com/mirrors/npm/
模块基础
npm install -g
是全局安装,该包会安装在nodejs目录的node_module下面
如果在一个名字为A的文件夹下面安装依赖库,并且这个依赖库的名字也是A的话,会报错,文件夹名字不能与安装的包的名字相同,这样会导致搜索包时的无限循环
nodejs中package.json和package-lock.json文件的功能分析
npm install 安装依赖包 --save、–save-dev、-S、-D的区别
Nvm ls-remote结果为N/A的解决方案
在环境变量里加上export NVM_NODEJS_ORG_MIRROR=https://npmmirror.com/mirrors/node/
在 TypeScript 中导入 JavaScript 包,解决声明文件报错问题
其实就是要去官网找@types
的js包。但是,比如react-transition-group
这个包,我需要同时安装react-transition-group
和@types/react-transition-group
才能用
TypeScript
Interface
TypeScript之interface定义对象(二)
ts里的interface更像是pojo
TypeScript 中的类和接口 - tireLYL的文章 - 知乎
可以看到interface编译之后什么都没有出来,因为ts编译之后就是js,而js本身没有类型,所有interface的对象在被ts编译后,就都变成了传统的无类型的js对象。因此,interface就仅仅是ts的一种语法(很像java的泛型和泛型擦除)
更进一步的,其实js里本身没有java里的那种真实的类,js里每个对象都仍然像是{a:1,b:"hello"}
的这种东西,两个对象同属于同一个类,真实情况下这两个对象只不过是恰好长得一样罢了。
typescript进阶(二):interface 和 class 的区别
undefined
Type ‘undefined’ is not assignable to type in TypeScript
The “Type ‘undefined’ is not assignable to type” error occurs when a possibly undefined value is assigned to something that expects a different type.To solve the error, use the non-null assertion operator or a type guard to verify the value is of the specific type before the assignment.
类
类型
typescript中,对象使用动态变量作为key获取相应的值时类型声明
export declare const PresetStatusColorTypes: readonly ["success", "processing", "error", "default", "warning"];
export type PresetStatusColorType = typeof PresetStatusColorTypes[number];
PresetStatusColorTypes
声明了一个“取值必须是只读列表”的一个对象,然后PresetStatusColorType
的类型就是PresetStatusColorTypes
列表中的任意一个,所以PresetStatusColorType
的实际类型,就是"success" | "processing" | "error" | "default" | "warning"
。
function f(t:PresetStatusColorType){
return ""
}
let t="success";
f(t as PresetStatusColorType)
最后如果不写t as PresetStatusColorType
,那么因为t
的类型是string
,与"success" | "processing" | "error" | "default" | "warning"
不符,就会报编译异常
React
安装
文档
https://beta.reactjs.org/learn
jsx、tsx
(005)RN开发 js jsx ts tsx的区别
就是Javascript和XML结合的一种格式,一个代码里,即能写js,也能写xml。tsx就是将js升级为ts,带上了类型
3.1 react的空标签
Lifecycle
React 18 生命周期
生命周期
组件的生命周期可分成三个状态,上面文章中提到的方法就是下面几个阶段的前后执行的:
- Mounting(挂载):已插入真实 DOM
- Updating(更新):正在被重新渲染
- Unmounting(卸载):已移出真实 DOM
useXXX
useXXX都是带来side-effect的内置函数,所谓side-effect指的是副作用,直译就是侧面的影响,如果一个函数的运行不会对外部的数据造成影响,那么说这个函数没有产生副作用,反之那么就说这个函数有副作用。比如useState就是为了给函数带来一些副作用,并且通过这些副作用来从而控制每次的输出。比如说分页请求,第一次渲染某个组件,我们会使用useState来保存页码,我们保存的这个页码数据和这个组件本身没关系,这个页码数据存储在和这个组件不相关的地方,因此产生了副作用,然后用户修改了分页大小或者做了翻页,那么我们就会调用setState去更新这个数据,并且会触发useState的机制,导致重新渲染这个页面的时候。
这块确实建议看官方文档,讲得很好
[
State: A Component’s Memory
(https://juejin.cn/post/6979786083680649223)
useState为什么不能放到条件语句里面?
useState为什么不能放在判断里,从问题深入到源码,盘它
useEffect
轻松学会 React 钩子:以 useEffect() 为例
React18的useEffect会执行两次
useEffect
是在挂载后执行,之所以执行两次,是因为开发模式下,多模拟了一次卸载和挂载,个人理解,比如说退出一个页面后重新进入这个页面,可能不需要再次请求这个页面所需的数据了,也就是所说的react未来希望支持的,卸载后,仍然保留状态。更进一步说,如果让同一个页面再次挂载,那会不会因为数据等问题(比如重复请求了数据)导致出错。为了规避这些问题,所以react强行模拟了一次重新加载。让开发人员将前端页面做得幂等。
后文中的例子,描述了一些卸载后重置页面状态等例子,但对于其中数据类的例子(3-1、3-2)个人觉得例子没有意义,这个只是利用了development模式下执行两次挂载的特性,强行让useEffect内部关键的一些功能,在第二次重新挂载的时候执行。但这和react这样的设计相悖。至于3-2的例子是有一点贴切的,比如重新挂载的时候,可以不要去请求数据了。
Synchronizing with Effects
Add cleanup if needed
章节讲了为啥开发模式会执行两次useEffect
以及cleanup函数的执行时机
React will call your cleanup function each time before the Effect runs again, and one final time when the component unmounts (gets removed).
如果仅仅是同步地对数据进行处理然后渲染,那么不应该使用useEffect,就直接在主函数处理就好了。
这篇文章还讲到了好多其他react特性,例如key、例如react函数的用法。
- key与useState的用法,笔记见后面的key部分内容。
- Adjusting some state when a prop changes :如果在render过程中执行了setXX导致当前组件re-render,那么react会直接丢弃掉第一次render的jsx(即未)然后直接开始重新渲染。第一次render仅仅就执行到生成了新的jsx,并未真正地进行渲染,因此子组件也未被触发re-render
- When you’re not sure whether some code should be in an Effect or in an event handler, ask yourself why this code needs to run. Use Effects only for code that should run because the component was displayed to the user. 这句话难理解一点,useEffect应该用于页面被展示出来后应该做的一些操作,例如请求数据、提交埋点等。而点击这种操作是由用户导致的事件,不应该用useEffect
- Initializing the application 对于某些整个app阶段只能执行一次的的代码,需要特殊处理,否则如果这些代码的组件被unmount->re-mount,那么这些代码就会被二次执行,可能导致错误。react18的开发模式为我们模拟了这种情况,提醒我们不要忘记这个问题。
useRef
useRef “Object is possibly null” error in React [Solved]
typescript里useRef需要泛型,并且会做非空判断。
property ‘click’ does not exist on type ‘never’ useref
React useRef 指南
坑
export default function A6Update(a6UpdateProps: A6UpdateProps)
这个组件,如果使用的时候
<A6Insert open={insertModalOpen} student={insertForm}>
</A6Insert>
会报错Property 'children' does not exist on type 'IntrinsicAttributes & A6InsertProps'
,需要写<A6Insert open={insertModalOpen} student={insertForm}></A6Insert>
也就是说,>
与</A6Insert>
不能分开,因为这个组件不具备内部元素
Key
是时候该知道React中的Key属性的作用与最佳实践了!
聊聊React中的key
React之key详解
对上文所说的“key相同且数据相同则不会重新渲染”应该是错的,因为子模块仍然会重新渲染,key也不具备“记忆功能”,key并不是用于优化性能的,而仅仅就用于标识一个组件的id,如果通过key判断发现key变了,那么这个组件就要重新生成,那么useState等信息不可以共用;反之如果某个组件没有key,那么react会认为就是一个组件,useState等信息可以共用。但再次声明,key不具备记忆能力,比如key从1->2->1,第二次变回1的时候,仅依赖key是想不起来上次1的组件样式是什么的。
CSS
在 React 中设置全局 font family
// index.tsx
// 👇️ import css
import './index.css';
import {createRoot} from 'react-dom/client';
import App from './App';
const rootElement = document.getElementById('root');
const root = createRoot(rootElement);
root.render(
<App />
);
// index.css
body,
html {
font-family: 'Poppins', sans-serif;
}
event
react-event-type-in-typescript
react的click事件的ts类型?
react组件之间的通信
React18 - react-transition-group
React18 - react-transition-group
React系列十四 - React过渡动画
React 学习 — 过渡动画
AntD
Form
Ant Design表单之labelCol 和wrapperCol的实际开发笔记
antd项目中input框的value值不能被修改,form.setFieldsValue 来动态改变表单值
Day.js
Axios
使用Typescript重构axios(十三)——让响应数据支持泛型
ts
里的axios
本身也支持泛型,比如axios.get<T>()