小型Q&A(置顶)

loading 2022年04月20日 290次浏览

1. 基本数据类型有哪些,复杂数据类型有哪些?为什么基本数据类型存在栈中,复杂数据类型存在堆中?

基本数据类型:String、Number、Boolean、Undefined、Null、BigInt、Symbol
大小固定,存放在栈里面,里面直接开辟一个空间,存放的是值

复杂数据类型(引用数据类型):通过new操作符创建的对象,如Object,Array,Date,Function,RegExp等:
存放在堆里面,首先在栈里面存放地址,地址在指向堆里面的实例,真正的对象实例存放在堆空间中

原因:
堆比栈要大,但是栈比堆的运行速度要快
基本数据类型比较稳定且占据内存少,所以放在空间小但速度快的栈中
复杂数据类型放在堆中的目的是不影响栈的效率,而是通过引用的方式去堆中查找

typeof用于确认原始类型,instanceof用于确认引用类型。

2.为什么 0.1+0.2 !== 0.3

js能表示并进行精确算术运算的整数范围是[-2 53 - 1, 2 53 - 1]

要计算0.1+0.2的结果,首先要把这两个十进制数转换成二进制相加,再转换成十进制,然而当0.1转化为二进制时,会出现无限循环,但是尾数部分最多只能表示53位,在进行对阶,移码相加等操作后最终得出一个二进制数,转为十进制数得到的结果为0.30000000000000004,是一个典型的精度丢失案例。

可以通过ES6新提出的Number.EPSILON去处理上述问题

var a = 0.1, b = 0.2, c = 0.3;
var result = (Math.abs(a + b - c) < Number.EPSILON);
console.log(result) // true

3."=="和"==="的区别

"==="表示恒等,首先比较两边的变量数据类型是否相等,其次比较两边的变量的数值是否相等;
"=="表示相等即仅仅比较两边变量的数值是否相等。如果两个操作数不是同一类型,那么相等运算符会尝试一些类型转换

undefined值是由null值派生而来的,因此ECMA将它们定义为表面上相等

4.NaN

有一个特殊的数值叫 NaN,意思是“不是数值”(Not a Number),用于表示本来要返回数值的操作失败了(而不是抛出错误)。

console.log(0/0); // NaN 
console.log(-0/+0); // NaN 

console.log(5/0); // Infinity 
console.log(5/-0); // -Infinity 

Nan不等于任何值。包括它自己。

console.log(NaN == NaN); // false

isNaN()函数。该函数接收一个参数,可以是任意数据类型,然后判断这个参数是否“不是数值”。把一个值传给 isNaN()后,该函数会尝试把它转换为数值。任何不能转换为数值的值都会导致这个函数返回true。

console.log(isNaN(NaN)); // true 
console.log(isNaN(10)); // false,10 是数值
console.log(isNaN("10")); // false,可以转换为数值 10 
console.log(isNaN("blue")); // true,不可以转换为数值
console.log(isNaN(true)); // false,可以转换为数值 1

6.函数中实现私有变量

可以通过在this上添加一个用于访问私有变量的方法来实现访问函数内变量

        function MyObject() {
            // 私有变量和私有函数 
            let privateVariable = 10;
            function privateFunction() {
                return false;
            }
            // 特权方法
            this.publicMethod = function () {
                console.log(privateVariable)
                return privateFunction();
            };
        }
        let obj = new MyObject();
        console.log(obj.publicMethod())

7.伪数组转为数组的方式

        const str = 'hello';

        //1 效率高于2
        console.log(Array.prototype.slice.call(str));
        //2
        console.log([].slice.call(str));
        //3
        console.log(Array.from(str));
        //4
        console.log(Array.of(...str));
        //5
        console.log(new Array(...str));

8.遍历对象属性的方法

        const obj = {'a' : 1,'b' : 2,'c' : 3}
        //1
        for(const i in obj){
            console.log(i , obj[i]);
        }
        //2 只返回自身可枚举的属性
        console.log(Object.keys(obj));
        //3 自身可枚举和不可枚举的属性都返回 对于数组额外返回一个length属性
        console.log(Object.getOwnPropertyNames(obj));

9.合并数组的方法

        let arr1 = [1,2,3];
        let arr2 = [4,5,6];
        //1
        arr1.push.apply(arr1 , arr2);
        //2
        arr1.concat(arr2);
        //3
        let arr3 = [...arr1 , ...arr2];

10. Object.is和===的区别

别的行为与===基本一致,只有两处不同:

  • +0不等于-0
  • NaN等于NaN
        +0 === -0 //true
        NaN === NaN // false

        Object.is(+0, -0) // false
        Object.is(NaN, NaN) // true

11.isNan和Number.isNaN的区别

两者最主要的区别是:Number.isNaN()不存在类型转换的行为

isNaN()通过判断传入的参数能否转换为Number类型来判断是否NaN,并非严格地判断。

Number.isNaN()则是通过'==='严格地判断

比如下面这个例子:
isNaN不能将'测试'转换成Number类型,转换失败就返回true。
Number.isNaN严格地判断'测试'是否===NaN,返回false。

console.log(isNaN('测试')) //true
console.log(Number.isNaN('测试')) //false

12.箭头函数

(1)没有自己的this,内部的this就是外层代码块的this

因此不能用作构造函数,因为new一个对象第三步就是把构造函数里的this指向这个对象,不能使用new.target关键字。
也不能通过call,apply,bind去改变this地指向。

(2)没有原型prototype,因此也就不能作为构造函数使用

因为new一个对象第二步就是使得new出来的新对象的proto指向构造函数的prototype,而箭头函数没有

(3)不能使用arguments对象

(4)不能用作generator函数,不能使用yield关键字

13.instanceof的3个弊端

instanceof用来确认构造函数的prototype属性是否出现在某个实例对象的原型链上

但是有3个弊端:
(1)对于基本类型的判断

//字面量创建
console.log(1 instanceof Number)//false
//实例创建
console.log(new Number(1) instanceof Number)//true

(2)检测结果未必准确

var arr = [1, 2, 3];
console.log(arr instanceof Array) // true
console.log(arr instanceof Object);  // true

function fn(){}
console.log(fn instanceof Function)// true
console.log(fn instanceof Object)// true

(3)不能检测undefined和null,需要用Object.prototype.toString.call()来检测

14.Object.prototype.toString.call()

Object原型上的toString和Number,Array,Boolean,Function等等原型上的toString都不同,作用并不是用来转换成字符串的

Object上的toString它的作用是返回当前方法执行的主体(方法中的this)所属类的详细信息即"[object Object]"。

其中第一个object代表当前实例是对象数据类型的(这个是固定死的),第二个Object代表的是this所属的类是Object。

Object.prototype.toString.call('') ;   // [object String]
Object.prototype.toString.call(1) ;    // [object Number]
Object.prototype.toString.call(true) ; // [object Boolean]
Object.prototype.toString.call(undefined) ; // [object Undefined]
Object.prototype.toString.call(null) ; // [object Null]
Object.prototype.toString.call(new Function()) ; // [object Function]
Object.prototype.toString.call(new Date()) ; // [object Date]
Object.prototype.toString.call([]) ; // [object Array]
Object.prototype.toString.call(new RegExp()) ; // [object RegExp]
Object.prototype.toString.call(new Error()) ; // [object Error]
Object.prototype.toString.call(document) ; // [object HTMLDocument]
Object.prototype.toString.call(window) ; //[object global] window是全局对象global的引用

15.展开运算符和rest运算符

首先两者格式都是'...'

区别主要在于:

展开运算符用于打开被封闭的项目,用来展开数组,对象等
rest运算符用于包装展展开的项目,比如用来给函数传参作为最后一个参数等

展开运算符实例:

        let a = [1,2,3];
        let b = [4,5,6];
        let c = [...a , ...b];
        console.log(c) //[1,2,3,4,5,6]

rest运算符实例:

        const [a, b, ...c] = [1, 2, 3, 4, 5, 6];
        console.log(c) //[3,4,5,6]

其实就是一个意思,不必纠结。

16. document和window

window代表浏览器中的一个打开的窗口或者框架。

window对象会在或者每次出现时被自动创建,在客户端JavaScript中,Window对象是全局对象。要引用当前的窗口不需要特殊的语法,可以把那个窗口属性作为全局变量使用,例如:可以只写document,而不必写window.document。

同样可以把窗口的对象方法当做函数来使用,如:只写alert(),而不必写window.alert

window对象实现了核心JavaScript所定义的全局属性和方法。

window对象的window属性和self属性引用都是他自己。

document是window对象的一部分,可通过 window.document 属性对其进行访问。

代表整个HTML文档,该对象使得我们可以从script中访问html页面中的元素。

HTMLDocument接口进行了扩展,定义HTML专用的属性和方法,很多属性和方法都是HTMLCollection对象,其中保存了对锚、表单、链接以及其他可脚本元素的引用。

18. useEffect和useLayoutEffect区别

useEffect是异步执行,执行时机是浏览器渲染完成之后。

而useLayoutEffect是同步执行(导致有可能造成阻塞),执行时机是DOM更新完成之后。

不管怎么说,useLayoutEffect总是比useEffect先执行的。
如果实在不知道用啥,官方推荐使用useEffect即可,适用于绝大多数情况。

在实际使用时如果想避免页面抖动/闪烁(在useEffect里修改DOM很有可能出现)的话,可以把需要操作DOM/调整样式等代码放在useLayoutEffect里。在这里做点dom操作,这些dom修改会和react做出的更改一起被一次性渲染到屏幕上,只有一次回流、重绘的代价。

19. rgba和opacity区别

两者都是用来设置透明度的,主要区别如下:

  1. opacity是一个属性,它的值可以被子元素继承,并且该元素及其继承了该属性的所有子元素的所有内容透明度都会改变
  2. rgba是一个属性值,rgba设置的元素只对该元素的颜色或背景色有改变,并且不会被继承

20. 隐藏元素的方法

三个的作用都是使得元素不可见,具体有以下区别:
display: none: 渲染树不会包含该渲染对象,因此该元素不会在页面中占据位置,也不会响应绑定的监听事件。

visibility: hidden: 元素在页面中仍占据空间,但是不会响应绑定的监听事件。

display:none是非继承属性,子孙节点会随着父节点从渲染树消失,通过修改子孙节点的属性也无法显示;
visibility:hidden是继承属性,子孙节点消失是由于继承了hidden,通过设置visibility:visible可以让子孙节点显示;
修改常规文档流中元素的 display 通常会造成文档的重排,但是修改visibility属性只会造成本元素的重绘;

opacity: 0: 将元素的透明度设置为 0,以此来实现元素的隐藏。元素在页面中仍然占据空间,并且能够响应元素绑定的监听事件。

transform: scale(0,0): 将元素缩放为 0,来实现元素的隐藏。这种方法下,元素仍在页面中占据位置,但是不会响应绑定的监听事件。

21. CSS选择器权重

  • 1000:行内样式
  • 0100:ID选择器
  • 0010:类选择器/属性选择器/伪类(如:hover)
  • 0001:标签选择器/伪元素(如::before)
  • 0000:通配符选择器/各种关系选择器(如子元素选择器>,相邻兄弟元素选择器+等)
  • 没有权重:继承过来的样式

22. z-index什么时候失效?

  • 父元素position为relative
    • 解决方案:父元素position改为absolute或static;
  • 元素没有设置position属性
    • 解决方案:将position设置为relative、absolute或fixed中的一种
  • 元素设置了浮动
    • 解决方案:去除浮动,改为display:inline-block

23.flex:1

flex属性是flex-grow:1,flex-shrink:1,flex-basis:0(别的数字也可以,一般用0,但不能用auto)三个属性的缩写。

flex-grow:表示当前元素占据的剩余空间份数,默认为0,即如果存在剩余空间也不占据;如果都设置为1,等分剩余空间;如果其中某个设置为n,占据的空间是flex-grow设置为1的n倍。

举个例子:3个div,每个div宽度均为200px,如果都不设置该属性:

如果都设置flex-grow:1,则3个div均分空间:

如果middle的flex-grow设置为2,则占50%空间,left和right占25%:

flex-shrink: 表示当前元素的缩小比例,默认为1,即如果项目空间不足,该项目将缩小。如果设置为0,空间不足时项目不会缩小。如果都设置为1,等比例缩小。如果设置为n,空间不足时缩小比例为设置为1的元素的n倍。

flex-basis:定义在分配多余空间之前,项目占据的主轴空间。默认设置为auto,即盒子原本的大小。

这里要注意的是,如果flex-basis设置为数字,比如100px,20%等,那么几个盒子将直接按照flex-grow划分整个父盒子;但是如果设置为auto,那么几个盒子将按照flex-grow划分剩余空间,然后再加上自己原本的宽度。

比如父盒子宽度为1200px,三个盒子分别设置成100px,200px,300px:

  • 如果设置flex:1 1 0,会发现成功均分;(因为设置了flex-basis为0,那么这三个盒子在主轴上占据的空间都被视作0了)
  • 如果设置flex:1 1 auto,剩余空间为1200-100-200-300=600,三个盒子均分200,则三个盒子新宽度为300,400,500,未实现均分效果。

24. Session和token

什么是Session?
当浏览器第一次访问服务器时,服务器创建一个session对象(该
对象有一个唯一的id,一般称之为sessionId),服务器会将sessionId
以cookie的方式发送给浏览器。
当浏览器再次访问服务器时,会将sessionId发送过来,服务器依据
sessionId就可以找到对应的session对象。

session是服务端存储的一个对象,主要用来存储所有访问过该服务端的客户端的用户信息(也可以存储其他信息),从而实现当页面跳转时保持用户会话状态。但是服务器重启或者会话过期时,内存会被销毁,存储的用户信息也就消失了。

不同的用户访问服务端的时候会在session对象中存储键值对,“键”用来存储开启这个用户信息的“钥匙”,在登录成功后,“钥匙”通过cookie返回给客户端,客户端存储为sessionId记录在cookie中。当客户端再次访问时,会默认携带cookie中的sessionId来实现会话机制。

session是基于cookie的,如果禁用cookie,session则失效。

但是这样就存在一个问题:A 网站和 B 网站是同一家公司的关联服务。现在要求,用户只要在其中一个网站登录,再访问另一个网站就会自动登录,请问怎么实现?

一种方案是session持久化,另一种方案就是token,也就是服务器索性不保存session数据了,只负责检验传过来的token的合法性。

一般通过JWT实现token加密解密

25. 为什么有时候⽤translate来改变位置⽽不用position?

translate 是 transform 属性的⼀个值。改变transform或opacity不会触发浏览器回流和重绘,但是改变position会触发回流和重绘。

这里用到的也就是CSS3的硬件加速:通过GPU渲染,解放CPU。

原理:DOM树和CSS树结合成渲染树,其中包含了大量的渲染元素,每一个渲染元素会被分到一个图层中,每个图层又会被加载到GPU形成渲染纹理,而图层在GPU中transform是不会触发回流重绘的,最终这些使用transform的图层都会由独立的合成器进程进行处理。

transform使浏览器为元素创建⼀个 GPU 图层,但改变绝对定位会使⽤到 CPU。 因此translate改变位置时,元素依然会占据其原始空间,绝对定位就不会发⽣这种情况。

26. px em/rem vw/vh

px是固定的像素,一旦设置了就无法因为适应页面大小而改变。

em相对于自身元素字体尺寸设置大小(如果自身没有设置,则相对于父元素字体尺寸设置大小),rem相对于根元素字体尺寸设置大小。rem更适用于响应式布局。

vw/vh分别是相对于视窗的宽度和高度,vmin是vw vh中的较小值,vmax是vw vh中的较大值。

27. 怎么获取盒子的宽高?

以前只了解过通过这种方法去获取:

 var oDiv1 = document.getElementById("div1");         
 console.log(oDiv1.style.width);

但是这种方法只能获取到行内样式,因此应该用以下这种更通用全面的方法:
window.getComputedStyle(element).width

var oDiv1 = document.getElementById("div1");
console.log(window.getComputedStyle(oDiv1).width) ;

28. 解构赋值注意点

注意id和data:number的区别,一个是使得id=42,一个是使得number=[867,5309]

let jsonData = {
  id: 42,
  status: "OK",
  data: [867, 5309]
};

let { id, status, data: number } = jsonData;

console.log(id, status, number);
// 42, "OK", [867, 5309]

29. PUT和POST到底有啥区别?

之前一直以为POST和PUT的区别容就是“POST用于创建资源,PUT用于更新资源”,实际上这种说法是不全面的,事实上二者均能用来创建资源,而二者最大的区别在于幂等性

(1) 使用PUT时,必须明确知道要操作的对象

PUT /customer/doc/1

{
    “name”: “John Doe”
}

PUT所对应的URI是要创建或更新的资源本身。

上面的PUT请求明确是对编号为1的文档进行操作,这里编号为1的文档就是要操作的对象。如果该文档不存在,就创建该文档;如果文档已经存在,就直接整个替换文档内容。

(2) POST不知道要操作的具体对象

POST /customer/doc/

{
    “name”: “John Doe”
}

POST所对应的URI并非创建的资源本身,而是资源的接收者。

POST请求并不知道要操作的对象,它只是向HTTP服务器提交一篇新文档,由HTTP服务器为该文档产生一个编号。

(3) 如果想用POST来实现更新呢?

POST /customer/doc/1

{
    “description”: “I am a student”
}

这里的含义是给编号为1的文档增加一个属性“description”。注意这里有两个不同:

  • 这里编号为1的文档必须是已经存在的文档,否则必须使用PUT
  • 这里是对目标对象的部分修改。只是增加了一个新属性“description”,之前的属性“name”不受影响。

(4) 总结
使用PUT时,必须明确知道要操作的对象,如果对象不存在,创建对象;如果对象存在,则全部替换目标对象。

但用POST创建对象时,之前并不知道要操作的对象,由HTTP服务器为新创建的对象生成一个唯一的URI;使用POST修改已存在的对象时,一般只是修改目标对象的部分内容。

那么上面提到的幂等是啥意思?意思就是相同的PUT请求不管执行多少次,结果都是一样的。但POST的每次执行都会改变结果。 哪怕是相同的POST请求也会在服务器端创建两份资源,它们具有不同的URI

29. GET和POST有什么区别?

  1. 数据传输方式:GET请求将参数包含在URL中,而POST请求通过请求体传递参数。

  2. 数据可见性:GET请求的参数对所有人都是可见的,因为它们是URL的一部分。而POST请求更具有隐蔽性。

  3. 数据长度:GET请求受到URL长度的限制,而POST请求对数据长度没有限制。

  4. 数据类型:GET请求只允许ASCII字符,而POST请求数据类型没有限制。

  5. 缓存:GET请求可以被缓存,而POST请求不会被缓存。GET 请求参数会被完整保留在浏览器历史记录⾥,⽽ POST 中的参数不会被保留。GET请求可以被收藏为书签,而POST请求不能被收藏为书签。

  6. 后退按钮/刷新:对于GET请求,后退按钮/刷新是无害的。而对于POST请求,数据会被重新提交(浏览器应该告知用户数据会被重新提交)。

  7. 服务器响应:GET请求产生一个TCP数据包,而POST请求产生两个TCP数据包。

对于 GET ⽅式的请求,浏览器会把 header 和 data ⼀并发送出去,服务器响应 200(返回数据);
⽽对于 POST,浏览器先发送 Header,服务器响应 100 continue,浏览器再发送 data,服务器响应 200 ok(返回数据)

  1. 幂等性:GET是幂等的,POST不是。(幂等意思是多次执⾏相同的操作,结果都是「相同」的。)

30. 正向代理和反向代理

正向代理: 正向代理是一个位于客户端和目标服务器之间的代理服务器。为了从目标服务器取得内容,客户端向代理服务器发送一个请求,并且指定目标服务器,之后代理向目标服务器转发请求,将获得的内容返回给客户端。

主要使用场景有翻墙,隐藏客户端IP等等。

反向代理: 反向代理是指以代理服务器来接收客户端的请求,然后将请求转发给内部网络上的服务器,将从服务器上得到的结果返回给客户端,此时代理服务器对外表现为一个反向代理服务器。

主要使用场景有负载均衡,隐藏服务器真实IP,提供安全保障等等。

31. typescript 任意属性

interface Person {
    name: string;
    age?: number;
    // 属性名类型是string就行,具体名称可自定义
    [propName: string]: any;
}

let tom: Person = {
    name: 'Tom',
    gender: 'male'
};

propName: 属性名
string: 属性名类型
any: 属性值类型

一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集,为什么?

假设propName属性名设置为name,属性值设置为number,那么就和上面的name: string冲突了,属性名相同但属性值类型不同。

32.type和interface区别

主要内容看这文章里的第4点,这里做一下总结:

  1. type能表示非对象类型,interface只能表示对象类型
  2. type通过&实现添加属性,interface可以通过extends继承
  3. type不允许重复声明,interface重复声明会自动合并
  4. interface不能包含属性映射(mapping),type可以
  5. this关键字只能用于interface
  6. type 可以扩展原始数据类型,interface 不行
  7. interface无法表达某些复杂类型(比如交叉类型和联合类型),但是type可以

综上所述,如果有复杂的类型运算,那么没有其他选择只能使用type;一般情况下,interface灵活性比较高,便于扩充类型或自动合并,建议优先使用。

33. 几种width的区别

先上代码

<!DOCTYPE html>
<html>

<body>

    <div id="myDiv" style="width:200px; padding:10px; border:5px solid black; overflow:auto;">
        <div style="width:300px; height:80px; margin:5px; padding:10px; border:5px solid black;"></div>
    </div>

    <button onclick="myFunction()">点击我</button>

    <p id="demo"></p>

    <script>
        function myFunction() {
            var div = document.getElementById("myDiv");
            var txt = "width: " + div.style.width;
            txt += "<br>clientWidth: " + div.clientWidth;
            txt += "<br>offsetWidth: " + div.offsetWidth;
            txt += "<br>scrollWidth: " + div.scrollWidth;
            document.getElementById("demo").innerHTML = txt;
        }
    </script>

</body>

</html>

再看效果图:

  • width:这是在样式中设置的宽度,值为200px。
  • clientWidth:这是元素的可视宽度,包括内边距,但不包括边框和滚动条。因此,值为200px(width) + 2 * 10px(padding) = 220px。
  • offsetWidth:这是元素的布局宽度,包括内边距、边框和滚动条。因此,值为200px(width) + 2 * 10px(padding) + 2 * 5px(border) = 230px。
  • scrollWidth:这是元素的内容宽度,包括由于溢出导致的视图中不可见内容。因此,值为300px(子div的width) + 2 * 10px(子div的padding) + 2 * 5px(子div的border) = 330px。

34. useParams和useSearchParams

useParamsuseSearchParams都是React Router的hooks,它们的主要区别在于它们访问URL的不同部分。

  • useParamsuseParams用于访问URL路径中的动态部分。例如,如果你有一个路由路径/user/:id,你可以使用useParams来获取:id的值。这对于创建根据URL变化而变化的页面非常有用。
https://test.com/user/47
  • useSearchParamsuseSearchParams用于访问URL的查询参数(即URL中?后面的部分)。这对于处理URL中的查询字符串非常有用,例如过滤和排序选项。
https://test.com/user?id=47

35. ts中一个文本类型的小问题

现在有这么一段代码,写req.method会报错:

类型“string”的参数不能赋给类型“"GET" | "POST"”的参数

而直接写"GET"就不会有这种问题

const req = { url: "https://example.com", method: "GET"};
function handleRequest(url: string, method: "GET" | "POST") {
    console.log('...')
}
handleRequest(req.url, req.method);
handleRequest(req.url, "GET");

因为在TypeScript中,req.method的类型被推断为string,而不是"GET" | "POST"。尽管req.method的实际值是"GET",但TypeScript并不会自动将它的类型缩小为"GET"。

要解决这个问题,有以下三种方法:

// Change 1:
const req = { url: "https://example.com", method: "GET" as "GET" };

// Change 2
handleRequest(req.url, req.method as "GET");

// Change 3
const req = { url: "https://example.com", method: "GET" } as const;

当你使用as const来定义req时,TypeScript会将req.method的类型推断为"GET",而不是string。这是因为as const会让TypeScript将对象的值推断为它们的字面量类型,而不是它们的宽类型。

36. 通过ts泛型实现Array.prototype.map()

function map<Input, Output>(arr: Input[], func: (arg: Input) => Output): Output[] {
    return arr.map(func);
}

const parsed = map(["1", "2", "3"], (n) => parseInt(n));

37. ts中解构函数的定义方式

正确的是这样的:

function sum({ a, b, c }: { a: number; b: number; c: number }) {
    console.log(a + b + c);
}
sum({ a: 10, b: 3, c: 9 });

别写成这样了:

function sum({ a: number, b: number, c: number }) {
    console.log(a + b + c);
}
sum({ a: 10, b: 3, c: 9 });

38. 为什么多个JSX标签需要被一个父元素包裹?

JSX 虽然看起来很像 HTML,但在底层其实被转化为了 JavaScript 对象,你不能在一个函数中返回多个对象,除非用一个数组把他们包装起来。这就是为什么多个 JSX 标签必须要用一个父元素或者 Fragment 来包裹。

39. margin/padding设置为百分比

在CSS中,当margin和padding的值设置为百分比时,这个百分比是相对于其父元素的宽度。这意味着,无论是margin-top、margin-bottom、padding-top还是padding-bottom,它们的百分比值都是基于父元素的宽度计算的。

例如,如果一个元素的父元素的宽度是200px,那么这个元素的margin-left: 10%;将会计算为20px。

40. 画一条0.5px的线

方法一 viewport meta(只适用于移动端):

<meta name="viewport" content="width=device-width, initial-scale=0.5, minimum-scale=0.5, maximum-scale=0.5"/>

方法二 transform:

.half-1px-line::after {
    content: "";
    position: absolute;
    width: 200%;
    height: 200%;
    border-width: 1px;
    border-style: solid;
    transform: scale(0.5, 0.5);
    pointer-events: none;
}

41. Babel的作用和工作原理

Babel是一个JavaScript编译器,主要用于将ECMAScript 2015+代码转换为向后兼容的JavaScript版本,以便在现有环境中运行。以下是Babel的工作原理和作用:

Babel的工作原理

  1. 解析(Parsing):这个阶段,Babel会使用@babel/parser对源代码进行解析,生成一个称为抽象语法树(AST)的数据结构。解析过程一般分为两个子阶段:

    • 词法分析:这个过程会将源代码字符串解析成一个个的令牌(Token)。每个令牌代表源代码中的一个语法单位,比如一个变量名、一个数字、一个操作符或者一个标点符号。
    • 语法分析:这个过程会将令牌组合成一个AST。
  2. 转换(Transformation):在这个阶段,Babel会使用@babel/traverse对AST进行深度优先遍历,调用插件对关注节点的处理函数,按需对AST节点进行增删改操作。这个阶段是Babel的核心,因为大部分Babel插件工作在这个阶段,对AST进行各种各样的转换。

  3. 生成(Generation):最后一个阶段,Babel会使用@babel/generator将转换后的AST重新生成为代码字符串。这个过程也被称为打印(Printing),在这个过程中,Babel会遍历转换后的AST,然后构建并输出转换后的代码字符串。

Babel的作用
Babel的主要作用是让你能够使用最新的JavaScript语法,而不用担心目标环境是否支持。它通过转换将新的语法特性编译成旧的等效代码,以确保在旧的浏览器或环境中也能正常运行。

举例说明:

假设我们有以下JavaScript代码:

function add(a, b) {
  return a + b;
}
let result = add(2, 3);
console.log(result);

就会生成如下AST:

Program
└─── FunctionDeclaration
  ├─── Identifier (name: "add")
  ├─── Identifier (name: "a")
  ├─── Identifier (name: "b")
  └─── BlockStatement
    └─── ReturnStatement
      └─── BinaryExpression
        ├─── Identifier (name: "a")
        ├─── Identifier (name: "b")
        └─── BinaryOperator (operator: "+")
─── VariableDeclaration
  ├─── VariableDeclarator
  │ ├─── Identifier (name: "result")
  │ └─── CallExpression
  │ ├─── Identifier (name: "add")
  │ ├─── NumericLiteral (value: 2)
  │ └─── NumericLiteral (value: 3)
  └─── ExpressionStatement
    └─── CallExpression
      ├─── Identifier (name: "console")
      └─── Identifier (name: "log")
      └─── Identifier (name: "result")

Babel会根据这个AST进行转换,然后生成目标代码。

42. meta标签的作用?

  1. 搜索引擎优化(SEO)

    <meta name="keywords" content="HTML, CSS, JavaScript">
    <meta name="description" content="This is a tutorial on HTML.">
    
  2. 定义页面使用编码

    <meta charset="UTF-8">
    
  3. 自动刷新并指向新的页面

    <meta http-equiv="refresh" content="30;url=https://www.example.com/">
    
  4. 控制页面缓冲

    <meta http-equiv="cache-control" content="no-cache">
    
  5. 设置视口

    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    

43. any和unknown的区别

any类型除了关闭类型检查,还有一个很大的问题,就是它会污染其他变量。它可以赋值给其他任何类型的变量(因为没有类型检查),导致其他变量出错。

let x:any = 'hello';
let y:number;

y = x; // 不报错

y * 123 // 不报错
y.toFixed() // 不报错

unknown也是可以表示任意类型,但是有一些限制,相当于严格版的any。

  1. unknown类型的变量,不能直接赋值给其他类型的变量(除了any类型和unknown类型)
let v:unknown = 123;

let v1:boolean = v; // 报错
let v2:number = v; // 报错

这就解决了上面的污染问题。

  1. 不能直接调用unknown类型变量的方法和属性
let v1:unknown = { foo: 123 };
v1.foo  // 报错

let v2:unknown = 'hello';
v2.trim() // 报错

let v3:unknown = (n = 0) => n + 1;
v3() // 报错
  1. unknown类型变量能够进行的运算是有限的

只能进行比较运算(运算符==、===、!=、!==、||、&&、?)、取反运算(运算符!)、typeof运算符和instanceof运算符这几种,其他运算都会报错。

let a:unknown = 1;

a + 1 // 报错
a === 1 // 正确

因此,只有经过类型缩小,unknown类型变量才可以使用。

let a:unknown = 1;
if (typeof a === 'number') {
  let r = a + 10; // 正确
}

let s:unknown = 'hello';
if (typeof s === 'string') {
  s.length; // 正确
}

45. react开发相较于原生开发的优势?

  • 数据驱动视图
  • 热重载
  • 单向数据流
  • 虚拟DOM/fiber/批处理
  • 生态系统(三方库、社区)
  • 跨平台(RN)

46. 双token的含义和作用

双Token校验是一种增强安全性的认证机制,通常使用两个Token来管理用户会话:一个短期有效的访问Token(Access Token)和一个长期有效的刷新Token(Refresh Token)。这种机制可以有效地减少Token泄露带来的风险,并提供更好的用户体验。

工作原理

  1. 用户登录

    • 用户通过登录表单提交用户名和密码。
    • 服务器验证用户凭证。
    • 如果验证成功,服务器生成两个Token:
      • 访问Token(Access Token):短期有效(如15分钟)。
      • 刷新Token(Refresh Token):长期有效(如7天或更长)。
  2. 存储Token

    • 访问Token通常存储在客户端的内存中或localStorage中。
    • 刷新Token通常存储在HttpOnlySecure的Cookie中,以防止XSS攻击。(服务器生成cookie,设置cookie属性并传给客户端,客户端自动储存,下一次请求服务器时自动携带发出)
  3. 请求携带Token

    • 客户端在每个请求中将访问Token添加到HTTP头部(通常是Authorization头部),发送给服务器。
  4. 服务器验证访问Token

    • 服务器在每个请求中验证访问Token的有效性。
    • 如果访问Token有效,服务器处理请求并返回响应。
  5. 访问Token过期

    • 当访问Token过期时,客户端使用刷新Token请求新的访问Token。
  6. 刷新Token

    • 客户端发送刷新Token到服务器,服务器验证刷新Token的有效性。
    • 如果刷新Token有效,服务器生成新的访问Token,并返回给客户端。
  7. 刷新Token过期

    • 当刷新Token过期时,用户需要重新登录。

优点

  • 安全性:即使访问Token泄露,由于其短期有效性,攻击者的利用时间也有限。刷新Token存储在HttpOnlySecure的Cookie中,减少了XSS攻击的风险。
  • 用户体验:用户在访问Token过期后无需重新登录,只需使用刷新Token获取新的访问Token。

缺点

  • 复杂性:实现双Token机制需要额外的逻辑和存储管理。
  • 刷新Token泄露风险:如果刷新Token泄露,攻击者可以获取新的访问Token,因此需要妥善保护刷新Token。