2024年前端最全前端最全面试题整理(持续更新)_前端面试题,2024年最新面试必备金句60条

刷面试题

刷题的重要性,不用多说。对于应届生或工作年限不长的人来说,刷面试题一方面能够尽可能地快速自己对某个技术点的理解,另一方面在面试时,有一定几率被问到相同或相似题,另外或多或少也能够为自己面试增加一些自信心,可见适当的刷题是很有必要的。

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

  • 前端字节跳动真题解析

  • 【269页】前端大厂面试题宝典

最后平时要进行自我分析与评价,做好职业规划,不断摸索,提高自己的编程能力和抽象思维能力。大厂面试远没有我们想的那么困难,摆好心态,做好准备,你也可以的。

  1. 防止私有变量被垃圾回收机制所清除

缺点:

  • 会造成内存泄漏

解决方法:

  • 将内层函数返回到全局的变量设为null,这种方法只能清除基本数据类型
4、说一下类的创建和继承
4.1 ES5类的创建和继承
类的创建(es5):new 一个 function,在这个 function 的 prototype 里面增加属性和方法。
// 定义一个动物类
function Animal (name) {
// 属性
this.name = name || 'Animal';
// 实例方法
this.sleep = function(){
console.log(this.name + '正在睡觉!');
}
}
// 原型方法
Animal.prototype.eat = function(food) {
console.log(this.name + '正在吃:' + food);
};

这样就生成了一个 Animal 类,实力化生成对象后,有方法和属性

类的继承

1. 原型链继承: 直接让子类的原型对象指向父类实例。当子类实例找不到对应的属性和方法时,就会往它的原型对象,也就是父类实例上找,从而实现对父类的属性和方法的继承

function Cat(){ }
Cat.prototype = new Animal();
Cat.prototype.name = 'cat';
// Test Code
var cat = new Cat();
console.log(cat.name);//cat
console.log(cat.eat('fish')); //cat正在吃:fish
console.log(cat.sleep());//cat正在睡觉!
console.log(cat instanceof Animal); //true
console.log(cat instanceof Cat); //true

优点:通过原型链继承的方式,原先存在父类型的实例中的所有属性和方法,现在也能存在于子类型的原型中了
缺点:

  • 由于所有子类实例原型都指向同一个父类实例, 因此对某个子类实例的父类引用类型变量修改会影响所有的子类实例;
  • 在创建子类实例时无法向父类构造传参, 即没有实现super()的功能

2. 构造函数继承: 在子类的构造函数中执行父类的构造函数,并为其绑定子类的this,通过call()函数,改变this指针的指向,让父类的构造函数把成员属性和方法都挂到子类的this上去

function Cat(name){
	Animal.call(this);
	this.name = name || 'Tom';
}
// Test Code
var cat = new Cat();
console.log(cat.name); //Tom
console.log(cat.sleep());//Tom正在睡觉!
console.log(cat instanceof Animal); // false
console.log(cat instanceof Cat); // true

优点:可以实现多继承,避免实例之间共享一个原型实例,能向父类构造方法传参
缺点:只能继承父类实例的属性和方法,不能继承原型上的属性和方法。

3. 组合继承 相当于构造函数继承和原型链继承的组合体。通过调用父类构造函数,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用

function Cat(name){
Animal.call(this);
this.name = name || 'Tom';
}
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;
// Test Code
var cat = new Cat();
console.log(cat.name);//Tom
console.log(cat.sleep());//Tom正在睡觉!
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); // true

优点:可以继承实例属性/方法,也可以继承原型属性/方法;组合继承拥有上面两种方法的优点。同时还能避免上面两种方法的缺点。
缺点:调用了两次父类构造函数,生成了两份实例

4. 寄生式继承 使用原型式继承可以获得一份目标对象的浅拷贝,然后利用这个浅拷贝的能力再进行增强,添加一些方法,这样的继承方式就叫作寄生式继承。

 let parent5 = {
    name: "parent5",
    friends: ["p1", "p2", "p3"],
    getName: function() {
      return this.name;
    }
  };
​
  function clone(original) {
    let clone = Object.create(original); //用于创建一个新对象,使用现有的对象来作为新创建对象的原型(prototype)
    clone.getFriends = function() {
      return this.friends;
    };
    return clone;
  }
​
  let person5 = clone(parent5);
​
  console.log(person5.getName());
  console.log(person5.getFriends());

优点:解决了组合继承中每次创建子类实例都执行了两次构造函数

缺点:

  • 原型继承存在的缺点他都存在。
  • 使用寄生式继承为对象添加方法,会由于不能做到方法的复用而降低效率,这一点和构造函数模式类似。

5. 寄生组合继承 使用寄生式继承来继承父类的原型,将结果指定给子类型的原型;砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性

function Parent6() {
    this.name = 'parent6';
    this.play = [1, 2, 3];
  }
   Parent6.prototype.getName = function () {
    return this.name;
  }
  function Child6() {
    Parent6.call(this);
    this.friends = 'child6';
  }
​
  function clone (parent, child) {
    // 这里改用 Object.create 就可以减少组合继承中多进行一次构造的过程
    child.prototype = Object.create(parent.prototype);
    child.prototype.constructor = child;
  }
​
  clone(Parent6, Child6);
  Child6.prototype.getFriends = function () {
    return this.friends;
  }
​
  let person6 = new Child6();
  console.log(person6); //{name: 'parent6', play: [1, 2, 3], friends: 'child6'}
  console.log(person6.getName()); //parent6
  console.log(person6.getFriends()); //child6

优点:

  • 集寄生式继承和组合式继承的优点与一身,实现基本类型继承的最有效方法。
  • 调用了一次父类
  • Child可以向Parent传参
  • 父类方法可以复用
  • 父类的引用属性不会被共享
4.2 ES6类的创建和继承
类的创建

1. 下面代码定义了一个“类”,可以看到里面有一个constructor方法,这就是构造方法

   class Box{//类名首字母必须大写,驼峰式命名
   		a=1;//描述改对象的属性值,ES7支持,不需要let或者var
       constructor(a,b){// 构造函数
           console.log(a+b);//8
       }
       play(){
           console.log(this===b)//this 就是通过构造函数实例化对象b,谁调用该方法,this就是谁
       }
   }
   let b=new Box(5,3)  //实例化,当实例化时执行构造函数constructor
   b.play()
   console.log(b.constructor === Box)//true 
    //对象的构造函数就是当前的类名
    
   console.log(b.constructor === Box.prototype.constructor)//true 
   //b是Box类的实例,它的constructor方法就是Box类原型的constructor方法

  • 类名首字母必须大写,驼峰式命名
  • 如果不继承任何类别,意味着自动继承Object,Object就是js所有类别的基类
  • 对象的构造函数就是当前的类名 ,构造函数是通过该类创建对象的唯一入口函数
  • 如果不写constructor构造函数,类别会自动添加这个函数默认没有内容或者执行超类的构造函数
  • 在其他语言中类名和构造函数名称一样,因为这个类需要实例化对象 ,必须先执行构造函数,有些语言的构造函数可以有多个
  • 原生js构造函数只能有一个,并且所有类的构造函数写为construstor函数,这个构造函数实际就是当前的类名

2. 因为js中构造函数就是类名,因此我们可以根据对象的构造函数是否是某个类名来判断它是否属于该类


//通过实例化创建的对象就是一个类,可以根据构造函数判断是否属于该类
	var arr=new Array(1,2,3);
	console.log(arr.constructor.name);//Array
	//单独打印constrctor是一个函数
	//构造函数有一个name属性就是这个类的类名
	console.log(arr.constructor===Array);//true
	
	var date=new Date();
	console.log(date.constructor===Date);//true
	
	var reg=/a/g;
	console.log(reg.constructor===RegExp);//true
	
	var div=document.createElement("div");
	console.log(div.constructor===HTMLDivElement);//true

3. 类中除了函数中的局部变量就是类中的属性,类中的this就是实例化对象

//以下代码只为解释定义一个人类的类,并且去如何使用它,中文编程万万不可以
   class man{
       eyes=2;
       mouse=1;
       ear=2;
       hands=2;
       foots=2;
       name;//创建了一个man的类,有以上属性
       constructor(\_name){
          this.name=_name;
       }
       walk(){//man的类有walk() run() eat() fire()的方法
           console.log(this.name+"走")
       }
       run(){
           console.log(this.name+"跑")
       }
       eat(){
           console.log(this.name+"吃")
       }
       fire(){
           console.log(this.name+"生火")
       }
   }

  var xiaoming=new man("小明");
  //创建一个xioaming的实例化对象,具备这个man的类的所有方法和属性,可以直接使用
  xiaoming.eat();
  xiaoming.run();
  var xiaohua=new man("小花");
  //创建了一个xioahua的实例化对象
  xiaohua.run();
  xiaohua.fire();

  1. 通过构造函数实例化的对象,就是这个类的实例对象(比如上述代码xiaohua就是通过构造函数实例化的对象,它就是这个类的实例对象)
  2. 因此这个实例对象就具备了类所有属性和方法,比如上述代码xiaohua就具备man这个类的所有属性和方法
  3. 这个实例对象就可以调用他自身的方法和属性了
  4. 因为构造函数默认生成实例化对象,因此不能在构造函数中使用return 返回其他内容,构造函数不允许写return
  5. 谁调用方法,在类的函数中this就是那个实例对象
类的继承

Class 可以通过extends关键字实现继承,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多。

比如,要继承Parent类,只需要使用extends就可以继承过来Parent的属性和方法了,继承父类后,必须在构造函数中使用super()调用超类的构造函数,超类的是什么,这个就执行的是什么;需要注意的是这里的super就可以理解为调用父类的constructor ,所以里面会传一些和父类相同的参数,而且必须写在最前面

ES6 的继承必须先调用super(),这样会生成一个继承父类的this对象,没有这一步就无法继承父类。这意味着新建子类实例时,父类的构造函数必定会先运行一次

class Child extends Parent{
       constructor(r){
           // 继承父类后,必须在构造函数中调用超类的构造函数
           super();//超类的构造函数,超类是什么,这个就是什么
           //超类的构造函数如果有参数的,在这里子类中必须传入这个需要参数
       }
   }

具体可查看该篇文章https://blog.csdn.net/weixin_44157964/article/details/103933204

5、如何解决异步回调地狱

promise、generator、async/await

6、 说说前端中的事件流

HTML 中与 javascript 交互是通过事件驱动来实现的,例如鼠标点击事件onclick、页面的滚动事件 onscroll 等等,可以向文档或者文档中的元素添加事件侦听器来预订事件。想要知道这些事件是在什么时候进行调用的,就需要了解一下“事件流”的概念

什么是事件流:事件流描述的是从页面中接收事件的顺序,DOM2 级事件流包括下面几个阶段

  • 事件捕获阶段:事件从Document节点自上而下向目标节点传播的阶段
  • 处于目标阶段:真正的目标节点正在处理事件的阶段
  • 事件冒泡阶段:事件从目标节点自下而上向Document节点传播的阶段

什么是冒泡?
冒泡是从下往上执行的,比如有个子事件和父事件,点击子事件后触发父事件,浏览器是默认事件冒泡的

事件委托
就是不在事件当前的dom上进行事件,而是在父级进行事件监听,通过事件冒泡来触发子事件
最常见的就是往ul 上绑定事件来触发 li

addEventListener:addEventListener 是 DOM2 级事件新增的指定事件处理程序的操作,这个方法接收 3 个参数:要处理的事件名、作为事件处理程序的函数和一个布尔值。最后这个布尔值参数如果是 true,表示在捕获阶段调用事件处理程序;如果是false,表示在冒泡阶段调用事件处理程序。

IE 只支持事件冒泡

7、如何让事件先冒泡后捕获

根据w3c标准,应先捕获再冒泡。若要实现先冒泡后捕获,给一个元素绑定两个addEventListener,其中一个第三个参数设置为false(即冒泡),另一个第三个参数设置为true(即捕获),调整它们的代码顺序,将设置为false的监听事件放在设置为true的监听事件前面即可。

还有一种,在执行捕获时,设置setTimeOut(方法名,0),把它放到下一个宏任务

8、说一下事件委托

事件委托指的是,不在事件的发生地(直接 dom)上设置监听函数,而是在其父元素上设置监听函数,通过事件冒泡,父元素可以监听到子元素上事件的触发,通过判断事件发生元素 DOM 的类型,来做出不同的响应。

举例:最经典的就是 ul 和 li 标签的事件监听,比如我们在添加事件时候,采用事件委托机制,不会在 li 标签上直接添加,而是在 ul 父元素上添加。

好处:比较合适动态元素的绑定,新添加的子元素也会有监听函数,也可以有事件触发机制。

事件代理在捕获阶段的实际应用

可以在父元素层面阻止事件向子元素传播,也可代替子元素执行某些操作。

9、说一下图片的懒加载和预加载
(1) 懒加载

懒加载:图片的懒加载指的是在长网页中延迟加载图像,是一种很好的优化网页性能的方式。用户滚动到他们之前,可视区域之外的图像是不会加载的。在某种情况下,他还可以帮助减少服务器负载,常适使图片很多,页面很长的电商网站场景中。

为什么要使用懒加载?

  • 可以提升用户体验:我们可以想一下,当用户打开手机淘宝的话,如果页面上全部的图片都需要加载,由于图片
    数量巨大,等待的时间就很长,用户体验就很不好。
  • 减少无用的资源加载:如果用户都没有去查看相应的内容,而我们却需要加载,此时就会增加浏览器的负担。
  • 防止并发资源过多导致阻塞js的加载。

懒加载实现原理

首先先将页面上的图片的src地址设置为空字符串,而图片上的真实路径设置在data-original属性中,当页面
发生滚动时需要去监听scroll事件,在scroll事件的回调中,判断我们的懒加载的图片是否进入可视区域,如
果图片在可视区域的话将src设置为data-original的值,这样就达到了延迟加载的效果。

(2) 预加载

预加载就是将所需的资源提前加载到本地,这样当用户使用到该资源时,就会自动从缓存中取出。

预加载实现方法

  1. 用html标签
<img src="http://pic26.nipic.com/20121213/6168183 0044449030002.jpg" style="display:none"/>


这样当我们进行加载页面时,也会加载src中的图片资源,此时该图片就会缓存到本地,当我们使用到该图片时,就会自动读取缓存中的内容。

  1. 使用Image对象,在js解析的时候加载
<script src='./myPreload.js'></script>

//myPreload.js文件
var image = new Image()
image.src="http://pic26.nipic.com/20121213/6168183 004444903000 2.jpg"


  1. 使用XMLHTTPRequest对象
    这里我是这样理解的,因为XMLHTTPRequest创建的是异步ajax请求对象,当我们加载完其他内容时,此时我们可以使用ajax进行加载资源,此时并不会对页面产生不好的效果。此时当我们加载完毕后,在后面如果我们使用到该资源时,就会从缓存中读取出来,加载就会很快。

两种技术的本质:

  • 两者的行为是相反的,一个是提前加载,一个是迟缓甚至不加载。
  • 懒加载对服务器有一定的缓解压力作用,预加载则会增加服务器压力

参考https://blog.csdn.net/weixin_47450807/article/details/123376938

10、常用的鼠标事件有哪些,mouseover 和 mouseenter 的区别

常用的鼠标事件分为三部分: 鼠标点击事件、鼠标经过事件、鼠标移动事件

鼠标点击事件
鼠标点击事件包括 click(单击)dblclick(双击)mousedown(按下)mouseup(松开)四个;其中 click 事件类型比较常用,而mousedownmouseup 事件类型多用在鼠标拖放、拉伸操作中

鼠标经过事件
mouseover:无论论鼠标指针穿过被选元素或其子元素,都会触发 mouseover 事件;有一个重复触发,冒泡的过程
mouseenter:只有在鼠标指针穿过被选元素时,才会触发 mouseenter 事件;不会冒泡

mouseout:不论鼠标指针离开被选元素还是任何子元素,都会触发 mouseout 事件;有一个重复触发,冒泡的过程
mouseleave:只有在鼠标指针离开被选元素时,才会触发 mouseleave 事件;不会冒泡

鼠标移动事件
mousemove 事件类型是一个实时响应的事件,当鼠标指针的位置发生变化时(至少移动一个像素),就会触发 mousemove 事件。该事件响应的灵敏度主要参考鼠标指针移动速度的快慢以及浏览器跟踪更新的速度

11、JS 的 new 操作符做了哪些事情
  1. 创建一个新对象
  2. 将这个新对象的原型指向构造函数的prototype
  3. 执行构造函数,将this指向新创建的对象
  4. 返回新创建的对象
    在这里插入图片描述
12、bind,apply,call 的区别

通过 apply 和 call 改变函数的 this 指向,他们两个函数的第一个参数都是一样的表示要改变指向的那个对象,第二个参数,apply 是数组,而 call 则是arg1,arg2…这种形式。 call和apply都会会立即执行该函数

通过 bind 改变 this 作用域会返回一个新的函数,这个函数不会马上执行。需要再调用一次

如果使用call、apply、bind时,第一个参数是null,就意味着将函数中this重定向到window

只有apply的参数是array

13、JS 的各种位置,比如 clientHeight,scrollHeight,offsetHeight ,以及scrollTop, offsetTop,clientTop 的区别?
  • clientHeight:表示的是可视区域的高度,不包含 border 和滚动条
  • offsetHeight:表示可视区域的高度,包含了 border 和滚动条
  • scrollHeight:表示了所有区域的高度,包含了因为滚动被隐藏的部分
  • clientTop:表示边框 border 的厚度,在未指定的情况下一般为 0
  • scrollTop:滚动后被隐藏的高度,获取对象相对于由 offsetParent 属性指定的父坐标(css
    定位的元素或 body 元素)距离顶端的高度

在这里插入图片描述

14、JS 拖拽功能的实现

如果要设置物体拖拽,那么必须使用三个事件,并且这三个事件的使用顺序不能颠倒。

  1. onmousedown:鼠标按下事件
  2. onmousemove:鼠标移动事件
  3. onmouseup:鼠标抬起事件

拖拽的基本原理就是根据鼠标的移动来移动被拖拽的元素。鼠标的移动也就是x、y坐标的变化;元素的移动就是style.position的top和left的改变。当然,并不是任何时候移动鼠标都要造成元素的移动,而应该判断鼠标左键的状态是否为按下状态,是否是在可拖拽的元素上按下的。

也可以通过 html5 的拖放(Drag 和 drop)来实现

15、JS异步加载
15.1 为什么要JS异步加载?

因为同步加载存在问题!
JS在默认情况下是以同步模式(又称阻塞模式)加载的,这里“加载”的意思是“解释、执行”。在最新版本的浏览器中,浏览器对于代码请求的资源都是瀑布式的加载,而不是阻塞式的,但是JS的执行总是阻塞的。这会引起什么问题呢?如果在页面中加载一些JS,但其中某个请求迟迟得不到响应,位于此JS后面的JS将无法执行,同时页面渲染也不能继续,用户看到的就是白屏

如果js在<head>标签中,如果JS迟迟无法加载,于是阻塞了后面代码的执行,页面得不到渲染
如果把JS代码放到</body>标签之前(这也是所提倡的页面结构),页面瞬间被渲染,问题似乎解决了,可是…如果我们在引用js代码的后面再写一段js代码,如果上个js请求阻塞了,后面的代码也不会加载,所以问题依然存在:改变JS的加载位置只能改变页面的渲染,JS还是会阻塞。

15.2 异步加载 JS 的方法
  1. defer
    • 等dom文档全部解析完(dom树生成)才会被执行。
  2. async
    • async是HTML5的新属性,该属性规定一旦脚本可用,则会异步执行(一旦下载完毕就会立刻执行)。
    • async属性仅适用于外部脚本(只有在使用src属性时)
15.3 async和defer看起来差不多呀?而且经常一起出现!来辨析一下:
  • 如果没有async和defer属性,那么浏览器会立即执行当前的JS脚本,阻塞后面的脚本;
  • 如果有async属性,加载和渲染后续文档的过程和当前JS的加载与执行并行进行(异步),它是乱序执行的,不管你声明的顺序如何,只要它加载完了就会执行
  • 如果有defer属性,加载后续文档元素的过程和JS的加载是并行进行(异步)的,但是JS的执行在所有元素解析完成之后进行,而且它是按照加载顺序执行脚本的
15、Ajax 解决浏览器缓存问题
  1. ajax 发送请求前加上 anyAjaxObj.setRequestHeader("If-Modified-Since","0")
  2. ajax 发送请求前加上 anyAjaxObj.setRequestHeader("Cache-Control","no-cache")
  3. URL 后面加上一个随机数: "fresh=" + Math.random()
  4. URL 后面加上时间搓:"nowtime=" + new Date().getTime()
  5. 如果是使用 jQuery,直接这样就可以了 $.ajaxSetup({cache:false})。
16、JS 的节流和防抖
16.1 防抖和节流
  • 防抖是将多次执行变为最后一次执行
  • 节流是将多次执行变为每隔一段时间执行
16.2 防抖和节流的场景

防抖的应用场景很多:

  • 输入框中频繁的输入内容,搜索或者提交信息;
  • 频繁的点击按钮,触发某个事件;
  • 监听浏览器滚动事件,完成某些特定操作;
  • 用户缩放浏览器的resize事件;
    总之,密集的事件触发,我们只希望触发比较靠后发生的事件,就可以使用防抖函数;

节流的应用场景:

  • 监听页面的滚动事件;
  • 鼠标移动事件;
  • 用户频繁点击按钮操作;
  • 游戏中的一些设计;

总之,依然是密集的事件触发,但是这次密集事件触发的过程,不会等待最后一次才进行函数调用,而是会按照一定的频率进行调用

16.3 防抖和节流的实现
  1. lodash、underscore第三方库来实现
  2. 防抖实现
        function debounce(fn, delay = 200) {
            let timer = 0

            return function () {
                if (timer) clearTimeout(timer)

                timer = setTimeout(() => {
                    fn.apply(this, arguments) // 透传 this 和参数
                    timer = 0
                }, delay)
            }
        }

        const input1 = document.getElementById('input1')
        input1.addEventListener('keyup', debounce(() => {
            console.log('发起搜索', input1.value)
        }), 300)

  1. 节流实现
        function throttle(fn, delay = 100) {
            let timer = 0

            return function () {
                if (timer) return

                timer = setTimeout(() => {
                    fn.apply(this, arguments)
                    timer = 0
                }, delay)
            }
        }

        const div1 = document.getElementById('div1')
        div1.addEventListener('drag', throttle((e) => {
            console.log('鼠标的位置', e.offsetX, e.offsetY)
        }))

17、JS 中的垃圾回收机制

必要性:由于字符串、对象和数组没有固定大小,所有当他们的大小已知时,才能对他们进行动态的存储分配。JavaScript 程序每次创建字符串、数组或对象时,解释器都必须分配内存来存储那个实体。只要像这样动态地分配了内存,最终都要释放这些内存以便他们能够被再用,否则,JavaScript 的解释器将会消耗完系统中所有可用的内存,造成系统崩溃。

这段话解释了为什么需要系统需要垃圾回收,JS 不像 C/C++,他有自己的一套垃圾回收机制(Garbage Collection)。JavaScript 的解释器可以检测到何时程序不再使用一个对象了,当他确定了一个对象是无用的时候,他就知道不再需要这个对象,可以把它所占用的内存释放掉了

垃圾回收常用的方法:标记清除、计数引用

  • 标记清除

    • 标记阶段:给所有的活动对象打上标记。
    • 清除阶段:回收没有标记的对象。缺点:碎片化。经过多轮空间分配和垃圾回收后,堆从一整片完整的空间被划分为了很多不连续的碎片,这就导致另外一个问题:分配速度受限。因为每次重新分配空间时,都要遍历所有空闲链表,去寻找足够大的分块,最坏的情况下,每次进行分配都要遍历整个空闲链表。
      著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  • 计数引用
    记录每个值的被引用的次数,当它被初始化,并被赋值给一个变量,引用为 1;如果另外一个变量引用了它,引用加 1;如果一个变量不再引用它,引用减 1。当一个值的引用数变为 0,那就说明它再没法被访问了,因此它成为垃圾,可以放心回收

缺点:无法回收循环引用的对象,造成内存泄漏

function problem() {
    let objectA = new Object();
    let objectB = new Object();
    objectA.someOtherObject = objectB;
    objectB.anotherObject = objectA;
}

在上面这个例子中,objectA 和 objectB 通过各自的属性相互引用,于是它们的引用数都是 2。函数运行结束,objectA 和 objectB 的引用次数各自减 1,但因为它们还引用着彼此,所以它们的引用次数永远不会置为 0,也就无法被回收

还有一些别的GC算法:复制算法、标记 - 整理算法、分代式垃圾回收、增量式垃圾回收 具体参考https://www.jianshu.com/p/20364ba1d7a6

18、eval

它的功能是将对应的字符串解析成 JS 并执行,如果传入的参数不是字符串,它会原封不动地将其返回

应该避免使用eval,因为非常消耗性能(2次,一次解析成 JS,一次执行)

eval缺点:

  • 降低性能:网上一些文章甚至说 eval() 会拖慢性能 10 倍,倒没有10倍这么夸张
  • 安全问题:因为它的动态执行特性,给被求值的字符串赋予了太大的权力,于是大家担心可能因此导致 XSS 等攻击。
  • 调试困难:eval 就像一个黑盒,其执行的代码很难进行断点调试。
19、如何理解前端模块化

前端模块化就是复杂的文件编程一个一个独立的模块,比如 JS 文件等等,分成独立的模块有利于重用(复用性)和维护(版本迭代),这样会引来模块之间相互依赖的问题,所以有了 commonJS 规范,AMD,CMD 规范等等,以及用于 JS 打包(编译等处理)的工具 webpack

20、说一下 CommonJS、ES6模块化、AMD、 CMD、UMD

JS模块化的演变经历了一个漫长的过程,从最初的CommonJS ,到后来的AMD和CMD,再到今天的ES6模块化方案

20.1 CommonJs

开始于服务器端的模块化,同步定义的模块化,每个模块都是一个单独的作用域,模块输出,modules.exports,模块加载 require()引入模块

为什么不在浏览器也是用CommonJS ?

CommonJS的 require 语法是同步的,当我们使用require 加载一个模块的时候,必须要等这个模块加载完后,才会执行后面的代码。如果知道这个事实,那我们的问题也就很容易回答了。NodeJS 是服务端,使用 require 语法加载模块,一般是一个文件,只需要从本地硬盘中读取文件,它的速度是比较快的。但是在浏览器端就不一样了,文件一般存放在服务器或者CDN上,如果使用同步的方式加载一个模块还需要由网络来决定快慢,可能时间会很长,这样浏览器很容易进入“假死状态”。所以才有了后面的AMD和CMD模块化方案,它们都是异步加载的,比较适合在浏览器端使用。

1. 导出
CommonJs中使用module.exports导出变量及函数,也可以导出任意类型的值

// 导出一个对象
module.exports = {
    name: "hh",
    age: 18
}
// 导出任意值
module.exports.name = "hh"

导出也可以省略module关键字,直接写exports导出也可以

exports.name = "hh"

混合导出,exportsmodule.exports可以同时使用,不会存在问题。

exports.name = "hh"
module.exports.age = 18

2. 导入

// index.js
module.exports.name = "hh"
module.exports.age = 18

let data = require("./index.js")
console.log(data) // { name: "hh", age: 18 }

重复导入:不管是CommonJs还是Es Module都不会重复导入,就是只要该文件内加载过一次这个文件了,我再次导入一次是不会生效的。

let data = require("./index.js")
let data = require("./index.js") // 不会再执行了

动态导入: CommonJs支持动态导入,什么意思呢,就是可以在语句中,使用require语法

let lists = ["./index.js", "./config.js"]
lists.forEach((url) => require(url)) // 动态导入

if (lists.length) {
    require(lists[0]) // 动态导入
}

导入值的变化 CommonJs导入的值是拷贝的,所以可以修改拷贝值,但这会引起变量污染,一不小心就重名

3. 总结
CommonJs解决了变量污染,文件依赖等问题,上面我们也介绍了它的基本语法,它可以动态导入(代码发生在运行时),不可以重复导入。

20.2 Es Module

在Es Module中导出分为两种,单个导出(export)、默认导出(export default),单个导出在导入时不像CommonJs一样直接把值全部导入进来了,Es Module中可以导入我想要的值。那么默认导出就是全部直接导入进来,当然Es Module中也可以导出任意类型的值。

1. 导出

// 导出变量
export const name = "hh"

// 导出函数也可以
export function fn() {}
export const test = () => {}


// 如果有多个的话
const name = "hh"
const sex = "male"
export { name, sex }

混合导出: 可以使用exportexport default同时使用并且互不影响,只需要在导入时地方注意,如果文件里有混合导入,则必须先导入默认导出的,在导入单个导入的值

export const name = "hh"
export const age = 18

export default {
    fn() {},
    msg: "hello hh"
}

2. 导入
Es Module使用的是import语法进行导入。如果要单个导入则必须使用花括号{} ,注意:这里的花括号跟解构不一样

// index,js
export const name = "hh"
export const age = 24

import { name, age } from './index.js'
console.log(name, age) // "hh" 24

// 如果里面全是单个导出,我们就想全部直接导入则可以这样写
import \* as all from './index.js'
console.log(all) // {name: "hh", age: 24}

混合导入: 混合导入,则该文件内用到混合导入,import语句必须先是默认导出,后面再是单个导出,顺序一定要正确否则报错

// index,js
export const name = "hh"
export const age = 24
export default {
    msg: "hh"
}

import msg, { name, age } from './index.js'
console.log(msg) // { msg: "hh" }

导入值的变化: export导出的值是值的引用,并且内部有映射关系,这是export关键字的作用。而且导入的值,不能进行修改也就是只读状态。

Es Module是静态: 就是Es Module语句``import只能声明在该文件的最顶部,不能动态加载语句,Es Module`语句运行在代码编译时

if (true) {
	import xxx from 'XXX' // 报错
}

3. 总结
Es Module也是解决了变量污染问题,依赖顺序问题,Es Module语法也是更加灵活,导出值也都是导出的引用,导出变量是可读状态,这加强了代码可读性

20.3 AMD和require.js

AMD规范采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。这里介绍用require.js实现AMD规范的模块化:用require.config()指定引用路径等,用define()定义模块,用require()加载模块。

20.4 CMD和sea.js

CMD是另一种js模块化方案,它与AMD很类似,不同点在于:AMD 推崇依赖前置、提前执行,CMD推崇依赖就近、延迟执行。此规范其实是在sea.js推广过程中产生的。

20.5 UMD

UMD是AMD和CommonJS的糅合
AMD模块以浏览器第一的原则发展,异步加载模块。CommonJS模块以服务器第一原则发展,选择同步加载,它的模块无需包装(unwrapped modules)。这迫使人们又想出另一个更通用的模式UMD (Universal Module Definition)。希望解决跨平台的解决方案。
UMD先判断是否支持Node.js的模块(exports)是否存在,存在则使用Node.js模块模式。在判断是否支持AMD(define是否存在),存在则使用AMD方式加载模块。

20.6 CommonJs和Es6 Module的区别
  • CommonJs导出值是拷贝,可以修改导出的值,这在代码出错时,不好排查引起变量污染;Es Module导出是引用值之前都存在映射关系,并且值都是可读的,不能修改
  • CommonJs可以动态加载语句,代码发生在运行时;Es Module是静态的,不可以动态加载语句,只能声明在该文件的最顶部,代码发生在编译时
  • CommonJs混合导出,当导出引用对象时之前的导出就被覆盖了;Es Module混合导出,单个导出,默认导出,完全互不影响
  • 循环加载时,CommonJs属于加载时执行。即脚本代码在require的时候就会全部执行,一旦出现某个模块被“循环加载”,就只输出已经执行的部分,还未执行的部分不会输出;es6模块时动态引用的,只要两个模块之间存在某个引用,代码就能够执行

参考https://juejin.cn/post/6938581764432461854#heading-18

21、对象克隆的简单实现

1. json暴力转化

通过JSON.stringify()JSON.parse() 将对象转为字符串之后在转为对象。

var obj = {name:'123'}
var obj2 = JSON.parse(JSON.stringify(obj)

这种简单粗暴的方式有局限性,当值为undefined、function、symbol会在转换过程中被忽略。

2. es6解构赋值

var obj = {name:'123',age:13};
var obj2 = {...obj}

只能深度拷贝对象的第一层,如果对象中的属性也是对象的话,没有办法进行深度拷贝的。

3. for in 循环遍历对象

  var obj = {
    name: "小明",
    age: 20
  }
  var obj1 = {}
  for (var key in obj) {
    obj1[key] = obj[key]
  }
  console.log(obj1);

同样的,只能深度拷贝对象的第一层,如果对象中的属性也是对象的话,没有办法进行深度拷贝的。

4. Object.assign() 对象的合并
利用Object.assign(), 第一个参数必须是空对象

var obj = {name:'123',age:13};
var obj2 = Object.assign({},obj1);

只能深度拷贝对象的第一层,如果对象中的属性也是对象的话,没有办法进行深度拷贝的。

5. 利用循环和递归的方式

function deepClone(obj, newObj) {
    var newObj = newObj || {};
    for (let key in obj) {
        if (typeof obj[key] == 'object') {
            newObj[key] = (obj[key].constructor === Array) ? [] : {}
            deepClone(obj[key], newObj[key]);
        } else {
            newObj[key] = obj[key]
        }
    }
    return newObj;
}

在循环递归中需要注意设置临界值(typeof obj[key] == ‘object’),否则会造成死循环
循环递归可以处理对象中嵌套数组或对象的问题。相当于第三种方法的优化

22、实现一个 once 函数,传入函数参数只执行一次
function ones(func){
	var tag=true;
	return function(){
		if(tag==true){
			func.apply(null,arguments);
			tag=false;
		}
		return undefined
	}
}

23、将原生的 ajax 封装成 promise
var myNewAjax=function(url){
	return new Promise(function(resolve,reject){
		var xhr = new XMLHttpRequest();
		xhr.open('get',url);
		xhr.send(data);
		xhr.onreadystatechange = function(){
			if(xhr.status == 200 && readyState == 4){
				var json=JSON.parse(xhr.responseText);
				resolve(json)
			}else if(xhr.readyState == 4 && xhr.status != 200){
				reject('error');
			}
		}
	})
}

24、JS 监听对象属性的改变 Object.defineProperty和Proxy

1. 在 ES5 中可以通过 Object.defineProperty 来实现已有属性的监听

defineProperty 只能劫持对象的某一个属性,不能对整个对象进行劫持,如果需要监听某一个对象的所有属性,需要遍历对象的所有属性并对其进行劫持来进行监听

let val = user.name
Object.defineProperty(user,'name',{
	set(newValue){
	val = newValue
	},
	get(){
	return val
	}
})

console.log(user.name) //调用get
user.name = 1  //调用set

缺点:如果 id 不在 user 对象中,则不能监听 id 的变化;数组对象是个特例,监听不到改变

2. 在 ES6 中可以通过 Proxy 来实现

const proxyData = new Proxy(data, {
 get(target, prop) {
   console.log('%c 调用get', 'color: green')
   return Reflect.get(target, prop)
 },
 set(target, prop, value) {
   console.log('%c 调用set', 'color: blue')
   return Reflect.set(target, prop, value) // Reflect通过代理对象更改目标对象的属性值
 }
})

console.log('proxyData.name -> ', proxyData.name) // Jane
console.log('data.name -> ', data.name) // Jane
proxyData.name = 'Jian'
console.log('proxyData.name -> ', proxyData.name) // Jian
console.log('data.name -> ', data.name) // Jian 

在这里插入图片描述

设置代理对象的属性后,原始对象和代理对象都发生了变化,但是获取原始对象的属性不会触发 getter ,只有访问代理对象的属性才能触发 getter,但是触发了 getter 和 setter 都是去给原始对象获取属性值和设置属性值,因为这里的 target 就是这个原始对象

3. Object.defineProperty和Proxy的区别

  1. defineProperty 是对属性劫持,proxy 是对对象代理

  2. defineProperty 无法监听对象新增属性,proxy 可以

    • 当对象新增属性的时候,defineProperty 没有对新增的属性进行劫持,自然就不会监听到对象新增的属性变化,而proxy 是对对象进行代理,自然就能监听到对象属性的新增
  3. defineProperty 无法监听对象删除属性,proxy 可以

    • proxy 有专门针对属性删除的方法 deleteProperty(和set,get同级),可以在对象属性被删除时触发
  4. defineProperty 监听数组的操作需要重载原型方法,proxy 不需要对数组的方法进行重载

    • 数组的 push、pop、shift、unshift、splice、sort,reverse是无法触发 set 方法的;
    • Vue 中能对数组的这些方法监听到是因为 Vue 源码对数组的这些方法进行了重载
  5. proxy 是浏览器支持的原生 API 直接通过浏览器的引擎就可以执行,defineProperty 是循环遍历对象属性的方式来进行监听,自然会比 proxy 对整个对象进行监听的方式要耗性能

参考https://blog.csdn.net/weixin_43443341/article/details/124041094

25、如何实现一个私有变量,用 getName 方法可以访问,不能直接访问
  1. 使用闭包
        (function (window) {
            var name = '我是私有变量,其他人都找不到我'
            window.getName = function () {
                return name
            }
        })(window)
        console.log(getName()) //'我是私有变量,其他人都找不到我'
        console.log(name) // undefined
        console.log(window.name) //undefined

  1. 构造函数
 function Private(){
  let a='私有变量';
  this.getName=function(){
 	 return a;
 }
}
let obj=new Private();
console.log(obj.a)  //undefine
console.log(obj.getName()); //私有变量

  1. 类构造器

class private {
	  constructor(){
		  let a='class私有';
		  this.getName=function(){
		  	return a;
		  }
	 }
}
let p=new private();
console.log(p.a); //undefine
console.log(p.getName());//class私有

26、= =和===、以及 Object.is 的区别
  • = =:等同,比较运算符,两边值类型不同的时候,先进行类型转换,再比较
  • = = =:恒等,严格比较运算符,不做类型转换,类型不同就是不等;
  • Object.is()是ES6新增的用来比较两个值是否严格相等的方法,与= = =的行为基本一致,不过有两处不同
    • +0不等于-0
    • NaN等于自身
+0 === -0 //true
NaN === NaN // false
Object.is(+0, -0) // false
Object.is(NaN, NaN) // true

27、setTimeout、setInterval 和 requestAnimationFrame 之间的区别
  1. setTimeout
    setTimeout()方法用来指定某个函数或字符串在指定的毫秒数之后执行。它返回一个整数,表示定时器的编号,这个值可以传递给clearTimeout()用于取消这个函数的执行
  2. setInterval

setInterval的用法与setTimeout完全一致,区别仅仅在于setInterval指定某个任务每隔一段时间就执行一次,也就是无限次的定时执行

[注意]:HTML5标准规定,setTimeout的最短时间间隔是4毫秒;setInterval的最短间隔时间是10毫秒,也就是说,小于10毫秒的时间间隔会被调整到10毫秒

  1. requestAnimationFrame
    大多数电脑显示器的刷新频率是60Hz,大概相当于每秒钟重绘60次。大多数浏览器都会对重绘操作加以限制,不超过显示器的重绘频率,因为即使超过那个频率用户体验也不会有提升。因此,最平滑动画的最佳循环间隔是1000ms/60,约等于16.6ms

而setTimeout和setInterval的问题是,它们都不精确。它们的内在运行机制决定了时间间隔参数实际上只是指定了把动画代码添加到浏览器UI线程队列中以等待执行的时间。如果队列前面已经加入了其他任务,那动画代码就要等前面的任务完成后再执行

requestAnimationFrame采用系统时间间隔,保持最佳绘制效率,不会因为间隔时间过短,造成过度绘制,增加开销;也不会因为间隔时间太长,使用动画卡顿不流畅,让各种网页动画效果能够有一个统一的刷新机制,从而节省系统资源,提高系统性能,改善视觉效果

使用
requestAnimationFrame的用法与settimeout很相似,只是不需要设置时间间隔而已。requestAnimationFrame使用一个回调函数作为参数,这个回调函数会在浏览器重绘之前调用。它返回一个整数,表示定时器的编号,这个值可以传递给cancelAnimationFrame用于取消这个函数的执行

//控制台输出1和0
var timer = requestAnimationFrame(function(){
    console.log(0);
}); 
console.log(timer);//1
cancelAnimationFrame(timer);

优点:

【1】requestAnimationFrame会把每一帧中的所有DOM操作集中起来,在一次重绘或回流中就完成,并且重绘或回流的时间间隔紧紧跟随浏览器的刷新频率
【2】在隐藏或不可见的元素中,requestAnimationFrame将不会进行重绘或回流,这当然就意味着更少的CPU、GPU和内存使用量
【3】requestAnimationFrame是由浏览器专门为动画提供的API,在运行时浏览器会自动优化方法的调用,并且如果页面不是激活状态下的话,动画会自动暂停,有效节省了CPU开销

参考https://www.cnblogs.com/xiaohuochai/p/5777186.html

28、自己实现一个 bind 函数

bind()方法创建一个新的函数,在bind()被调用时,这个新函数的this被bind的第一个参数指定,其余的参数将作为新函数的参数供调用时使用

var obj = {name:"Smiley"};
var greeting = function(str, lang){
    this.value = 'greetingValue';
    console.log("Welcome "+this.name+" to "+str+" in "+lang);
};
var objGreeting = greeting.bind(obj, 'the world'); 

var newObj = new objGreeting('JS');
console.log(newObj.value);

  • 我们通过使用call这个函数,让arguments也可以使用slice这个方法,从而通过slice(1)获取第二个参数以及以后的参数;
  • 之后,把得到的args传入apply函数,作为第二个参数。我们只需要把这个函数接受到的arguments,和之前args拼在一起成为一个数组就可以了。如果不拼,第二次传的参数JS就会是undefined
    arg=arg.concat(Array.prototype.slice.call(newArg));
  • 由于bind函数并不是立即执行,而是要返回一个函数,所以需要把return context.apply(obj,arg)包装在一个函数当中进行返回。
  • 在 new 一个 bind 过生成的新函数的时候,必须的条件是要继承原函数的原型,如果不继承this指向还是obj,newObj.value就会是undefined
Function.prototype.bind=function(obj,arg){
	var arg=Array.prototype.slice.call(arguments,1);
	var context=this;
	var bound=function(newArg){
		arg=arg.concat(Array.prototype.slice.call(newArg));
		return context.apply(obj,arg);
	}
	var F=function(){}
	//这里需要一个寄生组合继承
	F.prototype=context.prototype;
	bound.prototype=new F();
	return bound;
}

参考https://zhuanlan.zhihu.com/p/85438296

29、用 setTimeout 来实现 setInterval
	var timeWorker = {}
	var mySetInterval= function(fn, time) {
	// 定义一个key,来标识此定时器
	var key = Symbol();
	// 定义一个递归函数,持续调用定时器
	  var execute = function(fn, time) {
	     timeWorker[key] = setTimeout(function(){
	        fn();
	        execute(fn, time);
	     }, time)
	   }
	  execute(fn, time);
	  // 返回key
	  return key;
	}
	var myClearInterval = function(key) {
	  if (key in timeWorker) {
	   clearTimeout(timeWorker[key]);
	    delete timeWorker[key];
	  }
	}

	//使用
	let time1 = mySetInterval(() => {console.log(111)}, 3000);
	let time2 = mySetInterval(() => {console.log(222)}, 3000);
	//清除定时器
	setTimeout(() => {
		myClearInterval(time2);
	}, 4000)


30、JS 怎么控制一次加载一张图片,加载完后再加载下一张
  1. 使用promise中的all方法,实现将异步过程转换为阻塞式同步加载
	function loadImage(src) {
		return new Promise(function (resolve, reject) {
			var img = new Image();
			img.src = src; 
			img.onload = function () {
				resolve(img);
			};
			img.onerror = function () {
				reject("错误的地址:" + src);
			};
		});
	}
	var arr = [];
	for (var i = 5; i < 14; i++) {
		arr.push(loadImage("./img/" + i + "-.jpg"));
	}
	Promise.all(arr).then(function (list) {
		list.forEach((item) => {
			console.log(item.src, item.width, item.height);
		});
	});


  1. 使用promise中的async和await方法
	function loadImage(src) {
		return new Promise(function (resolve, reject) {
			var img = new Image();
			img.src = src; 
			img.onload = function () {
				resolve(img);
			};
			img.onerror = function () {
				reject("错误的地址:" + src);
			};
		});
	}
	async function loadAll() {
		var arr = [];
		for (var i = 5; i < 14; i++) {
			var img = await loadImage("./img/" + i + "-.jpg");
			arr.push(img);
		}
		console.log(arr);
	}
	loadAll();

31、 promise、process.nextTick、setTimeout代码的执行顺序
	setTimeout(function () {
		console.log(1);
	}, 0);
	new Promise(function (resolve, reject) {
		console.log(2);
		resolve();
	})
		.then(function () {
			console.log(3);
		})
		.then(function () {
			console.log(4);
		});
	process.nextTick(function () {
		console.log(5);
	});
	console.log(6);

输出 2,6,5,3,4,1

32、如何实现sleep函数

sleep函数作用是让线程休眠,等到指定时间在重新唤起。

  1. while 循环方式
	function sleep(ms) {
		var start = Date.now(),
			expire = start + ms;
		while (Date.now() < expire);
		console.log("1111");
		return;
	}

执行 sleep(1000)之后,休眠了 1000ms 之后输出了 1111。上述循环的方式缺点很明显,容易造成死循环

  1. 通过 promise 来实现
	function sleep(ms) {
		var temple = new Promise((resolve) => {
			console.log(111);
			setTimeout(resolve, ms);
		});
		return temple;
	}
	sleep(500).then(function () {
		console.log(222)
	});

先输出了 111,延迟 500ms 后输出 222

  1. 通过 async 封装
	function sleep(ms) {
		return new Promise((resolve) => setTimeout(resolve, ms));
	}
	async function test() {
		var temple = await sleep(1000);
		console.log(1111);
		return temple;
	}
	test();

  1. 通过 generate 来实现
	function\* sleep(ms) {
		yield new Promise(function (resolve, reject) {
			console.log(111);
			setTimeout(resolve, ms);
		});
	}
	sleep(500)
		.next()
		.value.then(function () {
			console.log(2222);
		});

33、 promise

单独开了个文章https://blog.csdn.net/weixin_44157964/article/details/129525061

34、Function._ proto _(getPrototypeOf)是什么?

有4个规则一定要记住,如下:

  • javascript中一切皆对象,函数也属于对象
  • 所有对象都含有__proto__
  • 只有函数才有prototype
  • 所有函数的默认原型都是Object的实例

如果把函数当做对象,那么Function就是它对应的构造函数,所以Function.__proto__==Function.prototype
Object并不是Function的构造函数,所以是false

Function.__proto__==Object.prototype //false
Function.__proto__==Function.prototype//true

35、js中的this指向

this主要分为以下几种

  1. 全局中this指向window

  2. 严格模式下,普通函数中的this指向 undefined (非严格模式指向window对象)

  3. 对象中的this : 属性中的this指向外层this ;方法中的this指向该对象

    • 对象中属性的this,指的是外层的this,因为这时候对象还没有创建完成
    • 对象中的方法是在对象执行完成以后才调用的.所以this就是当前对象
  4. 回调函数中的this指向window

  5. 事件回调函数中的this被修改成e.currentTarget(被侦听的对象)

  6. 箭头函数中的this指向指当前函数外

  7. ES6类中的this指向实例化对象 ,静态方法或者属性指向该类名(对于面向对象语言来说,一般在静态属性和方法中不允许使用this这个概念)

  8. ES5中的类中的this 原型上的属性和方法指向实例化对象,静态指向类名

  9. call apply bind 的this指向第一个参数

具体看我写的另一篇https://blog.csdn.net/weixin_44157964/article/details/103931479

36、JS 判断类型的几种方式
  1. typeof

    • string、number、boolean、undefined、function 、symbol、bigInt、object 返回对应类型字符串
    • 用typeof检测构造函数创建的Number,String,Boolean都返回object
    • 基本数据类型中:null 。引用数据类型中的:Array,Object,Date,RegExp。不可以用typeof检测。都会返回小写的object
  2. instanceof

    • instanceof 是用来判断 A 是否为 B 的实例,表达式为:A instanceof B,如果 A 是 B 的实例,则返回 true,否则返回 false。 需特别注意:instanceof 检测的是原型
    • 即instanceof 用来比较一个对象是否为某一个构造函数的实例。instanceof可以准确的判断复杂数据类型,但是不能正确判断基本数据类型。
    • 一句话理解其运算规则:instanceof 检测左侧的 __proto__ 原型链上,是否存在右侧的 prototype 原型。
  3. constructor

    • JavaScript中,每个对象都有一个constructor属性,可以得知某个实例对象,到底是哪一个构造函数产生的, constructor属性表示原型对象与构造函数之间的关联关系
    • 当一个函数F被定义时,JS引擎会为F添加prototype原型,然后在prototype上添加一个constructor属性,并让其指向F的引用,F利用原型对象的constructor属性引用了自身,当F作为构造函数创建对象时,原型上的constructor属性被遗传到了新创建的对象上,从原型链角度讲,构造函数F就是新对象的类型。这样做的意义是,让对象诞生以后,就具有可追溯的数据类型。
    • 通过typeof运算符来判断它是原始的值还是对象。如果是对象,就可以使用constructor属性来判断其类型
      注意:null 和 undefined 是没有 constructor 存在的,这两种类型的数据需要通过其他方式来判断。
  4. Object.prototype.toString.call()

    • Object.prototype.toString()是 Object 的原型方法,他会直接返回[object xxx]
    • 而像数组、function会重写toString方法
    • js中所有类都继承于Object,因此toString()方法应该也被继承了,所以可以使用Object.prototype.toString()
    • call指向当前的数据类型,就可以使用原型上的toString来判断了
37、数组常用的方法
序号方法作用返回值是否改变原数组
1join拼接为一个字符串,默认使用逗号作为分隔符新字符串不改变
2push从数组末尾向数组添加元素,可以添加一个或多个元素数组长度改变
3pop删除数组的最后一个元素删除的元素改变
4shift删除第一个删除的元素改变
5unshift向数组的开头添加一个或更多元素新的长度改变
6splice可以实现删除、插入和替换被删除元素组成的数组改变
7slice从a(下标)截取到b之前截取的数组不改变
8concat连接两个以及多个数组连接的新数组不改变
9indexOf查找元素,两个参数(元素,从哪位开始查)下标不改变
10laseIndexOf查找元素,从后往前,两个参数(元素,从哪位开始逆向查)下标不改变
11reverse翻转数组原数组改变
12sort排序,默认升序原数组改变
13forEach遍历无返回值
14map遍历返回原始数组处理后的数组不改变
15filter过滤返回满足条件的过滤后的数组不改变
16filles6新增,填充填充后的原数组改变
17every每一项满足返回truetrue或者false不改变
18some有一项满足返回truetrue或者false不改变
19includeses7新增,一个数组是否包含一个指定的值true或者false不改变
20reduce把上一次的结果参与计算处理后的结果不改变
21reduceRight同上,但是是从最后一项开始遍历处理后的结果不改变
22toString将数组每一项用,号连接转换为字符串字符串不改变
23toLocalString同上,但是可以传入拼接的参数字符串不改变
24find查找返回查找的值不改变
25findIndex查找符合条件的索引返回查找的索引不改变
26copyWithin浅复制数组的一部分到同一数组中的另一个位置,三个参数(复制到该位置,复制起始位置,复制结束位置之前)返回复制后的原数组改变
27flat多层数组展开,默认深度是1展开的新数组不改变
28flatMap对每个成员先执行map,再执行flat展开的新数组不改变
29entries遍历数组返回一个遍历器对象,可以用for…of遍历,对键值对进行遍历遍历器对象不改变
30keys同上,对键进行遍历遍历器对象不改变
31values同上,对值进行遍历遍历器对象不改变
38、数组去重
  1. 循环判断当前元素与其后面所有元素对比是否相等,如果相等删除;(执行速度慢)
var arr = [1,23,1,1,1,3,23,5,6,7,9,9,8,5];
function removeDuplicatedItem(arr) {
   for(var i = 0; i < arr.length-1; i++){
       for(var j = i+1; j < arr.length; j++){
           if(arr[i]==arr[j]){
             arr.splice(j,1);//console.log(arr[j]);
              j--;
           }
       }
   }
   return arr;
}

arr2 = removeDuplicatedItem(arr);
console.log(arr);
console.log(arr2);

  1. 借助新数组 判断新数组中是否存在该元素如果不存在则将此元素添加到新数组中
function unique4(arr){
 var res = [];
 for(var i=0; i<arr.length; i++){
  if(res.indexOf(arr[i]) == -1){
   res.push(arr[i]);
  }
 }
 return res;
}

console.log(unique4([1,1,2,3,5,3,1,5,6,7,4]));

  1. 利用对象属性存在的特性,如果没有该属性则存入新数组
function unique3(arr){
 var res = [];
 var obj = {};
 for(var i=0; i<arr.length; i++){
  if( !obj[arr[i]] ){
   obj[arr[i]] = 1;
   res.push(arr[i]);
  }
 } 
 return res;
}
console.log(unique3([1,1,2,3,5,3,1,5,6,7,4]));


  1. 先将原数组排序,再与相邻的进行比较(新数组最后一个元素),如果不同则存入新数组
function unique2(arr){
 var arr2 = arr.sort();
 var res = [arr2[0]];
 for(var i=1; i<arr2.length; i++){
  if(arr2[i] !== res[res.length-1]){
   res.push(arr2[i]);
  }
 } 
 return res;
}
 
console.log(unique2([1,1,2,3,5,3,1,5,6,7,4]));

  1. 定义一个新数组,并存放原数组的第一个元素,然后将原数组一一和新数组的元素对比,若不同则存放在新数组中
function unique(arr){
 var res = [arr[0]];
 for(var i=1; i<arr.length; i++){
  var repeat = false;
  for(var j=0; j<res.length; j++){
   if(arr[i] === res[j]){
    repeat = true;
    break;
   }
  }
  if(!repeat){
   res.push(arr[i]);
  }
 }
 return res;
}

console.log(unique([1,1,2,3,5,3,1,5,6,7,4]));


  1. 利用数组原型对象上的includes方法
function unique5(arr){
 var res = [];
  
 for(var i=0; i<arr.length; i++){
  if( !res.includes(arr[i]) ){ // 如果res新数组包含当前循环item
   res.push(arr[i]);
  }
 }
 return res;
}
 
console.log(unique5([1,1,2,3,5,3,1,5,6,7,4]));


  1. 利用数组原型对象上的 lastIndexOf 方法
function unique9(arr){
 var res = []; 
 for(var i=0; i<arr.length; i++){
  res.lastIndexOf(arr[i]) !== -1 ? '' : res.push(arr[i]);
 }
 return res;
}

console.log(unique9([1,1,2,3,5,3,1,5,6,7,4]));


  1. 利用 ES6的set 方法
function unique10(arr){
 //Set数据结构,它类似于数组,其成员的值都是唯一的
 return Array.from(new Set(arr)); // 利用Array.from将Set结构转换成数组
}
console.log(unique10([1,1,2,3,5,3,1,5,6,7,4]));


  1. 借助indexOf()方法判断此元素在该数组中首次出现的位置下标与循环的下标是否相等
var ar = [1,23,1,1,1,3,23,5,6,7,9,9,8,5];
function rep2(arr) {
    for (var i = 0; i < arr.length; i++) {
        if (arr.indexOf(arr[i]) != i) {
            arr.splice(i,1);//删除数组元素后数组长度减1后面的元素前移
            i--;//数组下标回退
        }
    }
    return arr;
}
var a1 = rep2(ar);
console.log(ar);
console.log(a1);

  1. 利用数组中的filter方法
var arr = ['apple','strawberry','banana','pear','apple','orange','orange','strawberry'];
 var r = arr.filter(function(element,index,self){
    return self.indexOf(element) === index;
 });
 console.log(r);

  1. 双重循环,判断当前元素是否与后边元素有重复,如果没有重复,push进新数组,有重复则将重复元素中最后一个元素push进新数组
var arr = [12, 2, 44, 3, 2, 32, 33, -2, 45, 33, 32, 3, 12];
var newArr = [];
for (var i = 0; i < arr.length; i++) {
    var repArr = [];//接收重复数据后面的下标
    //内层循环找出有重复数据的下标
    for (var j = i + 1; j < arr.length; j++) {
        if (arr[i] == arr[j]) {
            repArr.push(j);//找出后面重复数据的下标
        }
    }
    //console.log(repArr);
    if (repArr.length == 0) {//若重复数组没有值说明其不是重复数据
        newArr.push(arr[i]);
    }
}
console.log(newArr);//[ 44, 2, -2, 45, 33, 32, 3, 12 ]

参考https://www.cnblogs.com/web-record/p/9141373.html

39、去除字符串首尾空格
  • trim方法
  • 正则str.replace(/(^\s*)|(\s*$)/g, "")
  • split转换为数组,使用indexOf和lastIndexOf找第一个和最后一个不为空的值,使用splice截取空值数组,再使用join转换为字符串
40、能来讲讲 JS 的语言特性吗
  • 运行在客户端浏览器上;
  • 不用预编译,直接解析执行代码;
  • 是弱类型语言,较为灵活;
  • 与操作系统无关,跨平台的语言;
  • 脚本语言、解释性语言
41、如何判断是一个数组
  • Object.prototype.toString.call()
  • instanceof
  • constructor
  • Array.isArray()
42、跨域的原理

是指浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对 JavaScript 实施的安全限制,那么只要协议、域名、端口有任何一个不同,都被当作是不同的域。跨域原理,即是通过各种方式,避开浏览器的安全限制。

43、js实现跨域
  1. jsonp

    • JSONP是服务器与客户端跨源通信的常用方法。最大特点就是简单适用,老式浏览器全部支持,服务器改造非常小。
    • 只能实现get一种请求、不安全 容易遭到xss攻击
 <script> 
       function show(params) {
            console.log(params);
        }

 </script>    
<script src="https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su?wd=a&cb=show"></script>

  1. CORS
    CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制

    • 普通跨域请求:只服务端设置Access-Control-Allow-Origin即可,前端无须设置
    • 若要带cookie请求:前后端都需要设置。由于同源策略的限制,所读取的cookie为跨域请求接口所在域的cookie,而非当前页

    优缺点:

    1. 目前,所有浏览器都支持该功能(IE8+:IE8/9需要使用XDomainRequest对象来支持CORS)),CORS也已经成为主流的跨域解决方案。
    2. 整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。
    3. CORS与JSONP的使用目的相同,但是比JSONP更强大。JSONP只支持GET请求,CORS支持所有类型的HTTP请求。JSONP的优势在于支持老式浏览器,以及可以向不支持CORS的网站请求数据
  2. document.domain + iframe跨域
    此方案仅限主域相同,子域不同的跨域应用场景(网页一级域名相同,只是二级域名不同)。实现原理:两个页面都通过js强制设置document.domain为基础主域,就实现了同域

父窗口:(www.a.com/a.html)

<iframe id="iframe" src="http://child.a.com/b.html"></iframe>
<script>
    document.domain = 'a.com';
    var user = 'admin';
</script>

子窗口:(child.a.com/b.html)

<script>
    document.domain = 'a.com';
    // 获取父窗口中变量
    alert('get js data from parent ---> ' + window.parent.user);
</script>

  1. location.hash
    实现原理: a与b跨域相互通信,通过中间页c来实现(且c与a是同域)。 三个页面,不同域之间利用iframe的location.hash传值,相同域之间直接js访问来通信。

具体实现:A域:a.html -> B域:b.html -> A域:c.html,a与b不同域只能通过hash值单向通信,b与c也不同域也只能单向通信,但c与a同域,所以c可通过parent.parent访问a页面所有对象

a.html:(www.a.com/a.html)

<iframe id="iframe" src="http://www.b.com/b.html" style="display:none;"></iframe>
<script>
    var iframe = document.getElementById('iframe');

    // 向b.html传hash值
    setTimeout(function() {
        iframe.src = iframe.src + '#user=admin';
    }, 1000);
    
    // 开放给同域c.html的回调方法
    function onCallback(res) {
        alert('data from c.html ---> ' + res);
    }
</script>

b.html:(www.b.com/b.html)

<iframe id="iframe" src="http://www.a.com/c.html" style="display:none;"></iframe>
<script>
    var iframe = document.getElementById('iframe');

    // 监听a.html传来的hash值,再传给c.html
    window.onhashchange = function () {
        iframe.src = iframe.src + location.hash;
    };
</script>

c.html:(www.a.com/c.html)

<script>
    // 监听b.html传来的hash值
    window.onhashchange = function () {
        // 再通过操作同域a.html的js回调,将结果传回
        window.parent.parent.onCallback('hello: ' + location.hash.replace('#user=', ''));
    };
</script>

  1. window.name + iframe跨域
    浏览器窗口有window.name属性。这个属性的最大特点是,无论是否同源,只要在同一个窗口里,前一个网页设置了这个属性,后一个网页可以读取它。并且可以支持非常长的 name 值(2MB)

这种方法的优点是,window.name容量很大,可以放置非常长的字符串;缺点是必须监听子窗口window.name属性的变化,影响网页性能。
6. postMessage跨域
HTML5为了解决这个问题,引入了一个全新的API:跨文档通信 API(Cross-document messaging)。

这个API为window对象新增了一个window.postMessage方法,允许跨窗口通信,不论这两个窗口是否同源。

postMessage方法的第一个参数是具体的信息内容,第二个参数是接收消息的窗口的源(origin),即"协议 + 域名 + 端口"。也可以设为*,表示不限制域名,向所有窗口发送。

它可用于解决以下方面的问题:

* 页面和其打开的新窗口的数据传递
* 多窗口之间消息传递
* 页面与嵌套的iframe消息传递
* 上面三个场景的跨域数据传递
  1. nginx代理跨域

1、nginx配置解决iconfont跨域
浏览器跨域访问js、css、img等常规静态资源被同源策略许可,但iconfont字体文件(eot|otf|ttf|woff|svg)例外,此时可在nginx的静态资源服务器中加入以下配置

location / {
  add_header Access-Control-Allow-Origin \*;
}

2、 nginx反向代理接口跨域

跨域原理: 同源策略是浏览器的安全策略,不是HTTP协议的一部分。服务器端调用HTTP接口只是使用HTTP协议,不会执行JS脚本,不需要同源策略,也就不存在跨越问题

实现思路:通过nginx配置一个代理服务器(域名与domain1相同,端口不同)做跳板机,反向代理访问domain2接口,并且可以顺便修改cookie中domain信息,方便当前域cookie写入,实现跨域登录

#proxy服务器
server {
    listen       81;
    server_name  www.domain1.com;

    location / {
        proxy_pass   http://www.domain2.com:8080;  #反向代理
        proxy_cookie_domain www.domain2.com www.domain1.com; #修改cookie里域名
        index  index.html index.htm;

        # 当用webpack-dev-server等中间件代理接口访问nignx时,此时无浏览器参与,故没有同源限制,下面的跨域配置可不启用
        add_header Access-Control-Allow-Origin http://www.domain1.com;  #当前端只跨域不带cookie时,可为\*
        add_header Access-Control-Allow-Credentials true;
    }
}

  1. WebSocket

WebSocket protocol是HTML5一种新的协议。它实现了浏览器与服务器全双工通信,同时允许跨域通讯,是server push技术的一种很好的实现

WebSocket是一种通信协议,使用ws://(非加密)和wss://(加密)作为协议前缀。该协议不实行同源政策,只要服务器支持,就可以通过它进行跨源通信。

原生WebSocket API使用起来不太方便,可以使用Socket.io,它很好地封装了webSocket接口,提供了更简单、灵活的接口,也对不支持webSocket的浏览器提供了向下兼容
9. Nodejs中间件代理跨域

node中间件实现跨域代理,原理大致与nginx相同,都是通过启一个代理服务器,实现数据的转发,也可以通过设置cookieDomainRewrite参数修改响应头中cookie中域名,实现当前域的cookie写入,方便接口登录认证。
10. webpack-dev-server代理(vue中配置)

module.exports = {
    entry: {},
    module: {},
    ...
    devServer: {
        historyApiFallback: true,
        proxy: [{
            context: '/login',
            target: 'http://www.domain2.com:8080',  // 代理跨域目标接口
            changeOrigin: true,
            secure: false,  // 当代理某些https服务报错时用
            cookieDomainRewrite: 'www.domain1.com'  // 可以为false,表示不修改
        }],
        noInfo: true
    }
}

参考https://juejin.cn/post/6844903809118896135#heading-10

44、JS 数据类型

基本:String Boolean Number Undefined Null Symbol BigInt
引用:Object、Array、RegExp、Date、Function、特殊的基本包装类型(String、Number、Boolean)以及单体内置对象(Global、Math)

45、 实现 JS 中所有对象的深度克隆(Date 对象,正则对象)
function cloneDeep(obj) {
    if (obj == null) {
        return obj;
    }
    if (typeof obj !== 'object') {
        return obj;
    }
    if (obj instanceof RegExp) return new RegExp(obj);
    if (obj instanceof Date) return new Date(obj);
    let newObj = new obj.constructor;
    for (let key in obj) {
        newObj[key] = cloneDeep(obj[key])
    }
    return newObj;
}


46、不同数据类型的值的比较,是怎么转换的,有什么规则
  • == 相等 如果两边类型不一样,首先会隐式转化为相同的类型,然后再做比较
    • 对象==字符串 会把对象转换为字符串
    • null == undefined true(三个等号不相等) 但是null/undefined和其他任何值都不会相等
    • NaN==NaN false
    • Symbol()== Symbol() false
    • 剩余的情况(例如:对象= =数字 字符串= =布尔…) 都是要转换为数字,再进行比较的
  • === 绝对相等
  • Object.js([val],[val])
47、 null == undefined 为什么

要比较相等性之前,不能将 null 和 undefined 转换成其他任何值,但null == undefined会返回 true 。ECMAScript 规范中是这样定义的

48、暂停死区(暂时性死区)

在代码块内,使用 let、const 命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”

49、为什么var没有暂时性死区?

let/const作用域为块级作用域,变量不会提升;而var的作用域为全局作用域,可以进行变量提升,这也就是为什么var没有暂时性死区

50、有一个游戏叫做 Flappy Bird,就是一只小鸟在飞,前面是无尽的沙漠,上下不断有钢管生成,你要躲避钢管。然后小明在玩这个游戏时候老是卡顿甚至崩溃,说出原因(3-5 个)以及解决办法(3-5 个)

原因可能是:

  1. 内存溢出问题
  2. 资源过大问题
  3. 资源加载问题
  4. canvas 绘制频率问题

解决办法:

  1. 针对内存溢出问题,我们应该在钢管离开可视区域后,销毁钢管,让垃圾收集器回收钢管,因为不断生成的钢管不及时清理容易导致内存溢出游戏崩溃
  2. 针对资源过大问题,我们应该选择图片文件大小更小的图片格式,比如使用webp、png格式的图片,因为绘制图片需要较大计算量。
  3. 针对资源加载问题,我们应该在可视区域之前就预加载好资源,如果在可视区域生成钢管的话,用户的体验就认为钢管是卡顿后才生成的,不流畅
  4. 针对 canvas 绘制频率问题,我们应该需要知道大部分显示器刷新频率为60 次/s,因此游戏的每一帧绘制间隔时间需要小于 1000/60=16.7ms,才能让用户觉得不卡顿
51、什么是按需加载

按需加载是网站性能优化立竿见影的其中一项,按需加载可以了解为 当用户触发某个动作的时候,才主动去请求资源,这样带来的优化好处:减少了HTTP请求,节省宽带,让页面首屏的内容更快展现在用户的视线范围内,可见极大提高了用户体检。触发的动作有很多,如:鼠标点击、输入文字、拉动滚动条,鼠标移动、窗口大小更改等。加载的文件,可以是 JS、图片、CSS、HTML 等

52、说一下什么是 virtual dom

虚拟 dom 是相对于浏览器所渲染出来的真实 dom 的

以往,我们改变更新页面,只能通过首先查找dom对象,再进行修改dom的方式来达到目的。 但这种方式相当消耗计算资源, 因为每次查询 dom ,都需要遍历整颗 dom 树。

现在,我们用对象的方式来描述真实的 dom,并且通过对象与真实dom建立了一一对应的关系,那么每次 dom 的更改,我通过找到相应对象,也就找到了相应的dom节点,再对其进行更新。这样的话,就能节省性能,因为js 对象的查询,比对整个dom 树的查询,所消耗的性能要少。

Virtual DOM 本质上就是在 JS 和 DOM 之间做了一个缓存,就是用树型结构的JS对象来描述真实的DOM结构的信息,这个树结构的JS对象包含了整个DOM结构的信息

53、虚拟 DOM 的优缺点

优点:

  • 降低浏览器性能消耗
    因为Javascript的运算速度远大于DOM操作的执行速度,因此,运用patching算法来计算出真正需要更新的节点,最大限度地减少DOM操作,从而提高性能。
  • diff算法,减少回流和重绘
    通过diff算法,优化遍历,对真实dom进行打补丁式的新增、修改、删除,实现局部更新,减少回流和重绘
  • 跨平台
    虚拟 DOM 本质上是 JavaScript 对象,而 DOM 与平台强相关,相比之下虚拟 DOM ,可以进行更方便地跨平台操作,例如:服务器渲染、weex 开发等等

缺点:

  • 首次加载要慢些
    首次渲染大量DOM时,由于多了一层虚拟DOM的计算, 会比innerHTML插入慢
  • 无法进行极致优化
    虽然虚拟 DOM + 合理的优化,足以应对绝大部分应用的性能需求,但在一些性能要求极高的应用中 无法进行针对性的极致优化
54、webpack 用来干什么的

webpack 是一种前端资源构建工具,一个静态模块打包器(module bundler)。

在webpack 看来, 前端的所有资源文件(js/json/css/img/less/…)都会作为模块处理。

它将根据模块的依赖关系进行静态分析,打包生成对应的静态资源(bundle)。

55、ant-design 优点和缺点

优点:

  • 组件库丰富,提供了很多常用的 UI 组件,方便开发者使用。
  • 设计统一,提供了丰富的设计指南和设计资源,使得应用界面看起来统一且美观。
  • 文档齐全,提供了详细的使用文档和示例代码,方便开发者理解和使用。
  • 社区活跃,有许多开发者在使用和贡献 Ant Design,有较快的问题解决和更新速度

缺点:

  • 较大的体积,由于提供了丰富的组件,所以 Ant Design 的体积较大,可能会对应用的性能造成一定影响
  • 框架自定义程度低,默认 UI 风格修改困难
56、写一个函数,第一秒打印 1,第二秒打印 2

第一个是用 let 块级作用域

for(let i=0;i<5;i++){
	setTimeout(function(){
		console.log(i)
	},1000\*i)
}

第二个方法闭包

for(var i=0;i<5;i++){
	(function(i){
		setTimeout(function(){
			console.log(i)
		},1000\*i)
	})(i)
}

58、简单介绍一下 symbol

Symbol是ES6中引入的一种新的基本数据类型,用于表示一个独一无二的值。它是JavaScript中的第
七种数据类型,与undefined、null、Number(数值)、String(字符串)、Boolean(布尔值)、
Object(对象)并列

Symbol特点:

  • Symbol的值是唯一的,用来解决命名冲突问题
  • Symbol值不能与其他数据进行运算
  • Symbol定义的对象属性不能使用for…in循环遍历,但是可以使用Reflect.ownKeys来获取对象的所有键名(包括symbol),而且Object.getOwnPropertySymbols()返回一个给定对象自身的所有 Symbol 属性的数组
59、 什么是事件监听

事件分为DOM 0级事件和Dom 2级事件,DOM2级事件也叫做事件监听。DOM 0级事件的缺点是如果事件相同 后者的事件会覆盖前者的事件,DOM2级事件可以解决这个问题

document.onclick = function(){
    alert(1);
}
document.onclick = function(){
   alert(2);
}
//只会执行第二个函数 弹出2

DOM2级事件的方法(事件监听)

  • 主流浏览器 addEventListener() 【有三个参数】

    • 参数1:事件类型 不需要加on
    • 参数2:回调函数名称 (函数需要单独写在外面)
    • 参数3:布尔值 true代表捕获 false代表冒泡,默认false在冒泡阶段执行
    • 解绑事件方法:removeEventListener()
function a(){
    alert(1)
}
function b(){
    alert(2)
}
document.addEventListener("click",a);
document.addEventListener("click",b);     //点击后先执行第一个函数,再执行第二个函数
document.removeEventListener("click",a)   //不在执行a()这个函数

  • IE浏览器:attachEvent()

    • 参数1:事件类型 需要加on
    • 参数2:回调函数名称(函数需要单独写在外面)
    • 解绑事件方法:detachEvent()
function a(){
    alert(1)
}
function b(){
    alert(2)
}
document.attachEvent("onclick",a);
document.attachEvent("onclick",b);//点击后先执行第二个函数,再执行第一个函数
document.detachEvent("onclick",a)//不在执行a()这个函数

参考:https://www.cnblogs.com/Tian-yy/p/9060669.html

60、说说 C++,Java,JavaScript 这三种语言的区别
  • 从静态类型还是动态类型来看
    • 静态类型,编译的时候就能够知道每个变量的类型,编程的时候也需要给定类型,如Java中的整型 int,浮点型 float 等。C、C++、Java 都属于静态类型语言。
    • 动态类型,运行的时候才知道每个变量的类型,编程的时候无需显示指定类型,如JavaScript 中的 var、PHP 中的$。JavaScript、Ruby、Python 都属于动态类型语言
    • 静态类型还是动态类型对语言的性能有很大影响
  • 从编译型还是解释型来看
  • 编译型语言,像 C、C++,需要编译器编译成本地可执行程序后才能运行,由开发人员在编写完成后手动实施。用户只使用这些编译好的本地代码,这些本地代码由系统加载器执行,由操作系统的 CPU 直接执行,无需其他额外的虚拟机等。源代码=》抽象语法树=》中间表示=》本地代码
  • 解释性语言,像 JavaScript、Python,开发语言写好后直接将代码交给用户,用户使用脚本解释器将脚本文件解释执行。对于脚本语言,没有开发人员的编译过程,当然,也不绝对。
    源代码=》抽象语法树=》解释器解释执行
61、JS 原型链,原型链的顶端是什么?

null

62、Object 的原型是什么?Object 的原型的原型是什么?

Function.prototype(Object也是构造函数)

Object.prototype

63、在数组原型链上实现删除数组重复数据的方法

Array.prototype.unique = function(){
}

参考上面38数组去重

64、用闭包写个单例模式

定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
个人理解:通常做法就是一个变量初始值为空,每次创建之前判断这个变量,变量存在,复用之前的,不存在,则初始化

var Singleton = (function () {
    var instance;
    var CreateSingleton = function (name) {
        this.name = name;
        if (instance) {
            return instance;
        }
        // 打印实例名字
        this.getName();
        // instance = this;
        // return instance;
        return instance = this;
    }
    // 获取实例的名字
    CreateSingleton.prototype.getName = function () {
        console.log(this.name)
    }
    return CreateSingleton;
})();
// 创建实例对象 1
var a = new Singleton('a');
// 创建实例对象 2
var b = new Singleton('b');
console.log(a === b); //true

65、promise+Generator+Async 的使用
  • Promise 解决的问题:回调地狱

  • Generator 函数

    • 分段执行,可以暂停
    • 可以控制阶段和每个阶段的返回值
    • 可以知道是否执行到结尾
    function\* g() {
        var o = 1;
        yield o++;
        yield o++;
    }
    var gen = g();
    console.log(gen.next()); // Object {value: 1, done: false}
    var xxx = g();
    console.log(gen.next()); // Object {value: 2, done: false}
    console.log(xxx.next()); // Object {value: 1, done: false}
    console.log(gen.next()); // Object {value: undefined, done: true}
    
    

    generator 和异步控制: 利用 Generator 函数的暂停执行的效果,可以把异步操作写在 yield 语句里面,等到调用next 方法时再往后执行。这实际上等同于不需要写回调函数了,因为异步操作的后续操作可以放在 yield 语句下面,反正要等到调用 next 方法时再执行。所以,Generator 函数的一个重要实际意义就是用来处理异步操作,改写回调函数

  • async
    async 表示这是一个 async 函数,await 只能用在这个函数里面。
    await 表示在这里等待异步操作返回结果,再继续执行。
    await 后一般是一个 promise 对象

如果 async 函数返回的是一个同步的值,这个值将被包装成一个理解resolve 的Promise,等同于 return Promise.resolve(value)。
await 用于一个异步操作之前,表示要“等待”这个异步操作的返回值。await 也可以用于一个同步的值。

66、写个函数,可以转化下划线命名到驼峰命名
function underlineToHump(arr){
    //判断下划线位置,下划线位置加一,调用js的大写方法toUpperCase();
    let a = arr.indexOf('\_');
    let lit = arr.split('');
    lit[a+1] = lit[a+1].toUpperCase();
    arr = lit.join('');
    arr = arr.split('\_').join('');
    console.log(arr);
}
 
let arr = "as\_dad";
underlineToHump(arr);

67、JS 中 string 的 startsWith 和 indexof 两种方法的区别

startsWith() 方法用来判断当前字符串是否以另外一个给定的子字符串开头,并根据判断结果返回 true 或 false;有两个参数(要搜索的子字符,在 str 中搜索 searchString 的开始位置,默认值为 0)

Indexof 函数,indexof 函数可返回某个指定字符串在字符串中首次出现的位置

68、JS 字符串转数字的方法

通过函数 parseInt(),可解析一个字符串,并返回一个整数,语法为parseInt(string ,radix)

  • string:被解析的字符串
  • radix:表示要解析的数字的基数(以该进制读取,转换为十进制输出),默认是十进制,如果 radix<2 或>36,则返回NaN
  • 当忽略参数 radix , JavaScript 默认数字的基数如下:

如果 string 以 “0x” 开头,parseInt() 会把 string 的其余部分解析为十六进制的整数。
如果 string 以 0 开头,那么 ECMAScript v3 允许 parseInt() 的一个实现把其后的字符解析为八进制或十六进制的数字。
如果 string 以 1 ~ 9 的数字开头,parseInt() 将把它解析为十进制的整数。

69、let const var 的区别
  1. 重复声明
    var允许重复声明,let、const不允许
  2. 变量提升
    var会提升变量的声明到作用域的顶部,但let和const不会
  3. 暂时性死区
    var没有暂时性死区,let const有
  4. 块级作用域
    var没有块级作用域,let和const有块级作用域
  5. window对象的属性和方法(全局作用域中)
    全局作用域中,var声明的变量,通过function声明的函数,会自动变为window对象的变量,属性或方法,但const和let不会
70、什么是块级作用域

块级作用域是指变量或函数在一个代码块内有效,在代码块外无效的作用域。常见的代码块如if语句、for循环等。在 JavaScript 中,使用大括号 {} 定义块级作用域

71、如何用ES5 的方法实现块级作用域

众所周知,ES6新增了let关键字用来定义局部变量,它使得JS的变量有了块级作用域。
块级作用域是指变量只在{}内有效
那么在ES6以前如何给变量实现块级作用域呢?答案就是使用立即执行匿名函数

先看看没有块级作用域的情况:

function a(){
	for(var i=0;i<3;i++){
			
	}	
	console.log(i);
}
a();

上述例子中,将输出 i=3,因为i使用var声明,作用域是整个函数。
如果需要让变量i只在for循环中有效呢,我们可以这样实现:
即在for循环外面包裹一层匿名函数,在匿名函数外加圆括号表示立即执行

function a(){
    (function(){
        for(var i=0;i<3;i++){
            console.log(i);
        }	
    })();
 
    console.log(i); //输出undefined
}
a();

72、ES6 箭头函数的特性
  • 箭头函数没有 this,所有箭头函数内的this都是指当前函数外的this指向
  • 箭头函数没有自己的 arguments 对象,但是可以访问外围函数的arguments 对象
  • 不能通过 new 关键字调用,同样也没有 new.target 值和原型
73、setTimeout 和 Promise 的执行顺序
setTimeout(function () {
    console.log(1)
}, 0);
new Promise(function (resolve, reject) {
    console.log(2)
    for (var i = 0; i < 10000; i++) {
        if (i === 10) { console.log(10) }
        i == 9999 && resolve();
    }
    console.log(3)
}).then(function () {
    console.log(4)
})
console.log(5);

74、平时是怎么调试 JS 的

分为移动端调试和pc端调试

pc就是chorome控制台

移动端比较复杂,因为我们的项目都是在我们app内部,

  • 配置host
  • 本地起项目 npm run serve
  • fiddler抓包软件的配置。
  • 打开手机wifi,让手机和电脑处在同一个局域网下,并配置代理。
  • 在对应的环境中打开项目链接
  • 用数据线将手机和电脑连接
  • 打开chrome,在地址栏输入 chrome://inspect/#devices
75、基本数据类型和引用数据类型的区别
  • 基本数据类型的值是不可变的,任何方法都无法改变一个基本类型的值,当这个变量重新赋值后看起来变量的值是改变了,但是这里变量名只是指向变量的一个指针,所以改变的是指针的指向改变,该变量是不变的,但是引用类型可以改变
  • 基本数据类型不可以添加属性和方法,但是引用类型可以
  • 基本数据类型的赋值是简单赋值,如果从一个变量向另一个变量赋值基本类型的值,会在变量对象上创建一个新值,然后把该值复制到为新变量分配的位置上,引用数据类型的赋值是对象引用
  • 基本数据类型的比较是值的比较,引用类型的比较是引用的比较,比较对象的内存地址是否相同
  • 基本数据类型是存放在栈区的,引用数据类型是保存在栈区和堆区
76、NaN是什么的缩写

Not a Number

77、JS 的作用域类型
  • 全局作用域:变量在函数外定义为全局变量,全局变量有全局作用域:网页中的所有脚本和函数均可使用
  • 函数作用域:变量在函数内部声明
  • 块级作用域:在 { } 中
78、undefined与 null 的区别

null是表示一个"无"的对象,指向空的对象,这个对象是不存在的,转为数值时为0。null的使用场景:经常用作函数的参数,或作为原型链的终点
undefined是表示一个"无"的原始值,变量被声明了但还没有赋值,就为undefined,转为数值时为NaN

相同点:都是表示值的空缺,所以两者等于(= =)是相等的

不同点:null的类型是Object对象,undefined的类型就是Undefiend,所以两者全等(= = =)不相等

79、setTimeout(fn,100);100 毫秒是如何权衡的

setTimeout()函数只是将事件插入了任务列表,必须等到当前代码执行完,主线程才会去执行它指定的回调函数,有可能要等很久,所以没有办法保证回调函数一定会在setTimeout指定的时间内执行,100毫秒是插入队列的时间+等待的时间

80、怎么获得对象上的属性

首先定义一个对象

//定义对象
var obj = {
    ['str']: 'String property', [Symbol()]: 'Symbol property',
}

//定义不可枚举的字符串属性
Object.defineProperty(obj, 'unenum', {
    value: ' Non-enumerable property', writable: true, enumerable: false, configurable: true,
});

//定义不可枚举的Symbol属性
Object.defineProperty(obj, Symbol('unenum'), {
    value: 'Non-enumerable Symbol value', writable: true, enumerable: false, configurable: true,
});

//在原型链上定义一个字符串属性和一个Symbol属性
Object.setPrototypeOf(obj, { foo: 'bar', [Symbol('foo')]: 'bar' });

  1. Object.keys() & Object.values() & Object.entries 这三个方法都是为了来获得对象的属性与值的,最终返回值是一个数组,不过只获取对象本身的可枚举字符串属性
console.log(Object.keys(obj)); //["str"]
console.log(Object.values(obj)); // ["String property"]
console.log(Object.entries(obj)); //[["str", "String property"]]

  1. Object.getOwnPropertyNames()是获取对象自身上的字符串属性,包括可枚举的与不可枚举的属性,最后返回一个数组
console.log(Object.getOwnPropertyNames(obj)); //["str", "unenum"]

  1. Object.getOwnPropertySymbols() 是获取对象自身上的Symbol属性,包括可枚举的与不可枚举的,最后返回一个数组
console.log(Object.getOwnPropertySymbols(obj)); //[Symbol(), Symbol(unenum)]

  1. for...in.. 用来遍历对象上可枚举的字符串属性的,包括原型链上的可枚举的字符串属性。
for(let key in obj){
	console.log(key); //"str" "foo"
}

81、简单讲一讲 ES6 的一些新特性
  1. let和const
  2. symbol
  3. 模板字符串
  4. 字符串新方法 includes()、startsWith() 、endsWith()、repeat()、padStart()、padEnd()
  5. 解构表达式
    • 数组解构
    • 对象解构
  6. 对象方面
    • Map和Set
    • 数组的新方法: Array.from()、includes()、map()、filter()、some()、every() 、reduce()
    • object的新方法:Object.is()、Object.assign()、Object.keys()、Object.values()、Object.entries()等方法
    • 对象声明简写 let person = {name,age}
    • …(对象扩展符)
  7. 函数方面
    • 参数默认值
    • 箭头函数
  8. class(类)
  9. promise和proxy
  10. 模块化 import export
  11. 运算符
    • … 扩展运算符
    • 可选链 ?.
    • 函数绑定运算符::

参考https://blog.csdn.net/ZLJ_999/article/details/124122540

82、给出以下代码,输出的结果是什么原因?
for (var i = 0; i < 5; i++) {
    setTimeout(function () {
        console.log(i);
    }, 1000);
}
console.log(i)

在一秒后输出 5 个 5

每次 for 循环的时候 setTimeout 都会执行,但是里面的 function 则不会执行被放入任务队列,因此放了 5 次;for 循环的 5 次执行完之后不到 1000 毫秒;1000 毫秒后全部执行任务队列中的函数,所以就是输出 5 个 5。

83、如果已经有三个 promise,A、B 和 C,想串行执行,该怎么写?
// promise
A.then(B).then(C).catch(...)

// async/await
(async () => {
    await a();
    await b();
    await c();
})()

84、知道 private 和 public 吗

public:public 表明该数据成员、成员函数是对所有用户开放的,所有用户都可以直接进行调用

private:private 表示私有,私有的意思就是除了 class 自己之外,任何人都不可以直接使用

85、原型题
Function.prototype.a = 1; //所有函数的原型
Object.prototype.b = 2;//所有对象的原型
function A() { }
var a = new A();
console.log(a.a, a.b); 
console.log(A.a, A.b); 

86、promise 和 await/async 的关系

async-await是promise和generator的语法糖
async 函数返回的是一个promise 对象

87、JS 加载过程阻塞,解决方法

指定 script 标签的 async 属性。

如果 async=“async”,脚本相对于页面的其余部分异步地执行(当页面继续进行解析时,脚本将被执行)

如果不使用 async 且 defer=“defer”:脚本将在页面完成解析时执行

88、说 promise,没有 promise 怎么办

没有 promise,可以用回调函数代替

89、arguments

arguments 是类数组对象,有 length 属性,不能调用数组方法

可用 Array.from()转换

90、JavaScript 中的轮播实现原理?假如一个页面上有两个轮播,你会怎么实现

图片轮播的原理就是图片排成一行,然后准备一个只有一张图片大小的容器,对这个容器设置超出部分隐藏,在控制定时器来让这些图片整体左移或右移,这样呈现出来的效果就是图片在轮播了

如果有两个轮播,可封装一个轮播组件,供两处调用

分割线 :下面是待更新的题
  1. 简单实现 Node 的 Events 模块
  2. JS 的全排列
  3. 编写代码,满足以下条件: (1)Hero(“37er”);执行结果为Hi! This is 37er
    ( 2) Hero(“37er”).kill(1).recover(30); 执 行 结 果 为 Hi! This is 37er Kill 1 bug
    Recover 30 bloods(3)Hero(“37er”).sleep(10).kill(2)执行结果为Hi! This is 37er //
    等待 10s 后 Kill 2 bugs //注意为 bugs (双斜线后的为提示信息,不需要打印)
  4. 给两个构造函数 A 和 B,如何实现 A 继承 B?
  5. assign 的深拷贝
  6. Eventloop
  7. 实现计算一年中有多少周?

前端核心

一、服务端编程

1、JSONP 的缺点

JSON 只支持 get,因为 script 标签只能使用 get 请求;
JSONP 需要后端配合返回指定格式的数据。

2、dom 是什么,你的理解?

文档对象模型(Document Object Model,简称 DOM),是 W3C 组织推荐的处理可扩展标志语言的标准编程接口。在网页上,组织页面(或文档)的对象被组织在一个树形结构中,用来表示文档中对象的标准模型就称为 DOM

3、关于 dom 的 api 有什么
  • 节点查找API

    • document.getElementById :根据ID查找元素,大小写敏感,如果有多个结果,只返回第一个;
    • document.getElementsByClassName:根据类名查找元素,多个类名用空格分隔,返回一个 HTMLCollection 。注意兼容性为IE9+(含)。另外,不仅仅是document,其它元素也支持 getElementsByClassName 方法;
    • document.getElementsByTagName:根据标签查找元素, * 表示查询所有标签,返回一个 HTMLCollection 。
    • document.getElementsByName:根据元素的name属性查找,返回一个 NodeList
    • document.querySelector:返回单个Node,IE8+(含),如果匹配到多个结果,只返回第一个。
    • document.querySelectorAll :返回一个 NodeList ,IE8+(含)
    • document.forms :获取当前页面所有form,返回一个 HTMLCollection ;
  • 节点创建API

    • createElement创建元素
    • createTextNode创建文本节点
    • cloneNode 克隆一个节点
    • createDocumentFragment
  • 节点修改API

    • appendChild
    • insertBefore
    • insertAdjacentHTML
    • Element.insertAdjacentElement()
    • removeChild
    • replaceChild
  • 节点关系API

    • 父关系API:parentNode
    • 子关系API:children childNodes firstChild lastChild
    • 兄弟关系型API : previousSibling nextSibling previousElementSibling nextElementSibling
  • 元素属性型API

    • setAttribute 给元素设置属性
    • getAttribute
    • hasAttribute
  • 样式操作API

    • 直接修改元素的样式
    elem.style.color = 'red';  
    elem.style.setProperty('font-size', '16px');  
    elem.style.removeProperty('color');
    
    
    • 动态添加样式规则
    var style = document.createElement('style');  
    style.innerHTML = 'body{color:red} #top:hover{background-color: red;color: white;}';  
    document.head.appendChild(style);
    
    
    • classList获取样式属性
    了解dom节点样式(classList)的remove, add, toggle, contains, replace等方法的使用。
    
    
    • window.getComputedStyle 通过 element.sytle.xxx 只能获取到内联样式,借助 window.getComputedStyle 可以获取应用到元素上的所有样式,IE8或更低版本不支持此方法
    var style = window.getComputedStyle(element[, pseudoElt]);。
    
    

参考https://blog.csdn.net/weixin_43613849/article/details/121560210

二、Ajax

1、什么是ajax?ajax作用是什么?

异步的javascript和xml AJAX 是一种用于创建快速动态网页的技术。 ajax用来与后台交互

2、原生js ajax请求有几个步骤?分别是什么
//创建 XMLHttpRequest 对象
var ajax = new XMLHttpRequest();
//规定请求的类型、URL 以及是否异步处理请求。
ajax.open('GET',url,true);
//发送信息至服务器时内容编码类型
ajax.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); 
//发送请求
ajax.send(null);  
//接受服务器响应数据
ajax.onreadystatechange = function () {
    if (obj.readyState == 4 && (obj.status == 200 || obj.status == 304)) { 
    }
};

3、json字符串转换集json对象、json对象转换json字符串
//字符串转对象
JSON.parse(json)
eval('(' + jsonstr + ')')   
// 对象转字符串
JSON.stringify(json)

4、ajax返回的状态

分为readyState(状态值)和status(状态码)

readyState,是指运行AJAX所经历过的几种状态,无论访问是否成功都将响应的步骤,可以理解成为AJAX运行步骤,使用“ajax.readyState”获得

status,是指无论AJAX访问是否成功,由HTTP协议根据所提交的信息,服务器所返回的HTTP头信息代码,使用“ajax.status”获得

总体理解:可以简单的理解为state代表一个整体的状态。而status是这个大的state下面具体的小的状态

readyState

readyState总共有5个状态值,分别为0~4,每个值代表了不同的含义

  • 0:初始化,XMLHttpRequest对象还没有完成初始化
  • 1:载入,XMLHttpRequest对象开始发送请求
  • 2:载入完成,XMLHttpRequest对象的请求发送完成
  • 3:解析,XMLHttpRequest对象开始读取服务器的响应
  • 4:完成,XMLHttpRequest对象读取服务器响应结束

status

  • 1xx:信息响应类,表示接收到请求并且继续处理
  • 2xx:处理成功响应类,表示动作被成功接收、理解和接受
  • 3xx:重定向响应类,为了完成指定的动作,必须接受进一步处理
  • 4xx:客户端错误,客户请求包含语法错误或者是不能正确执行
  • 5xx:服务端错误,服务器不能正确执行一个正确的请求
5、如果我想发出两个有顺序的ajax 需要怎么做?

发出两个有顺序的 ajax,可以用回调函数,也可以使用 Promise.then 或者async 等

6、Ajax Fetch axios 比有什么优缺点?
  • ajax是最早出现发送后端请求的技术,属于原生 js
  • fetch 首先解决了回调地狱的问题,他返回的结果是一个 Promise 对象
  • axios 功能非常强大,包括 取消请求,超时处理,进度处理等等。但它的本质还是 ajax,基于 Promise 进行封装,既解决回调地狱问题,又能很好地支持各个浏览器。

ajax 的优缺点:

  • 属 js 原生,基于XHR进行开发,XHR 结构不清晰。
  • 针对 mvc 编程,由于近来vue和React的兴起,不符合mvvm前端开发流程。
  • 单纯使用 ajax 封装,核心是使用 XMLHttpRequest 对象,使用较多并有先后顺序的话,容易产生回调地狱
    fetch 的优缺点:
  • 属于原生 js,脱离了xhr ,号称可以替代 ajax技术。
  • 基于 Promise 对象设计的,可以解决回调地狱问题。
  • 提供了丰富的 API,使用结构简单。
  • 默认不带cookie,使用时需要设置。
  • 没有办法检测请求的进度,无法取消或超时处理。
  • 返回结果是 Promise 对象,获取结果有多种方法,数据类型有对应的获取方法,封装时需要分别处理,易出错。
  • 浏览器支持性比较差

axios的优缺点:

  • 在浏览器中创建XMLHttpRequest请求,在node.js中创建http请求
  • 解决回调地狱问题
  • 自动转化为json数据类型
  • 支持Promise技术,提供并发请求接口
  • 可以通过网络请求检测进度
  • 提供超时处理
  • 浏览器兼容性良好
  • 有拦截器,可以对请求和响应统一处理

三、移动 web 开发

1、知道 PWA 吗

概念

PWA 全称 Progressive Web App,即渐进式 WEB 应用。一个 PWA 应用首先是一个网页, 可以通过 Web 技术编写出一个网页应用. 随后添加上 App Manifest 和Service Worker来实现 PWA 的安装和离线等功能

纵观现有 Web 应用与原生应用的对比差距,如离线缓存、沉浸式体验等等,可以通过已经实现的 Web 技术去弥补这些差距,最终达到与原生应用相近的用户体验效果

特性

  • 安全可靠
    使用 Service Work 技术实现即时下载,当用户打开应用后,页面资源的加载不再完全依赖于网络,而是使用 Service Work 缓存离线包存在本地,确保为用户提供即时可靠的体验。
  • 访问更快
    首屏可以部署在服务端,节省网页请求时间,加载速度更快,拥有更平滑的动态效果和快速的页面响应。
  • 响应式界面
    支持各种类型的终端和屏幕。
  • 沉浸式体验
    在支持 PWA 的浏览器和手机应用上可以直接将 Web 应用添加到用户的主屏幕上,无需从应用商店下载安装。从主屏幕上打开应用之后,提供沉浸式的全屏幕体验。

功能

  • 手机应用配置(Web App Manifest)
    可以通过 manifest.json 文件配置,使得可以直接添加到手机的桌面上。
  • 离线加载与缓存(Service Worker+Cache API )
    可以通过 Service Worker + HTTPS +Cache Api + indexedDB 等一系列 Web 技术实现离线加载和缓存。
  • 消息推动与通知(Push&Notification )
    实现实时的消息推送与通知
  • 数据及时更新(Background Sync )
    后台同步,数据及时更新

优势与劣势

  1. 优势
    • 超简单的安装和下载
    • 发布迭代不需要第三方平台审核
      我们都知道发布一个苹果应用是需要提交 App Store 商店进行审核,通过了方可发布成功的。安卓应用也是一样。并且更新迭代版本的时候也需要审核,还需要提交一些功能说明,图片等资料。但是网页版的应用就完全不需要这个审核过程,直接部署服务器就可以使用
    • 渐进式
      现有的 Web 项目可以通过 PWA 的几个核心技术点一步步转型成一个完整的 PWA 应用
  2. 劣势
    • 谷歌
      基于 Chromium 开发的浏览器 Chrome 和 Opera 已经完全支持 PWA 。
      这里说一下 Chromium 和 Chrome 的区别。
      Chromium 是谷歌的开源项目,由开源社区去维护。拥有众多的版本包括Windows、Mac、Linux。国内所有的 “双核浏览器”,都是基于 Chromium 开发的,而我们下载的 Chromium 浏览器是其源码未经修改的直接编译版本。
      Chrome 是基于 Chromium 开发的,是闭源的,跨平台多端支持,特性更加丰富。
      Google上线了两个新网站,web.dev 和 squoosh.app 都支持 PWA( web.dev 是宣传和推广 PWA 的,解释了 PWA 的几个关键技术。squoosh.app 是一个图片压缩工具) 。
    • 微软
      微软将 PWA 带到了 Windows 10。同时 Windows Edge(windows 10 之后微软推出的浏览器,比 IE更流畅、外观 UI 更舒适) 也支持 PWA。
    • IOS
      随着 iOS 11.3 的发布,iOS 正式开始支持 PWA,可以将它放在苹果手机主屏。
    • Android
      Twitter 和 Flipboard 都推出了 PWA,可以将它放在安卓手机主屏。
    • 国内
      国内支持 PWA 的应用有微博、淘宝、豆瓣和饿了么。

参考https://blog.csdn.net/weixin_44135121/article/details/105528430

2、flex布局及优缺点

优点在于其容易上手,根据 flex 规则很容易达到某个布局效果
缺点是:浏览器兼容性比较差,只能兼容到 ie9 及以上

3、Rem 布局及其优缺点

优点:可以快速适用移动端布局 字体图片 高度

缺点:

  • 目前 ie 不支持,对 pc 页面来讲使用次数不多;
  • 数据量大:所有的图片,盒子都需要我们去给一个准确的值;才能保证不同机型的适配;
  • 在响应式布局中,必须通过 js 来动态控制根元素 font-size 的大小。也就是说 css 样式和 js 代码有一定的耦合性。且必须将改变 font-size 的代码放在css 样式之前
4、 百分比布局缺点
  1. 计算困难,如果我们要定义一个元素的宽度和高度,按照设计稿,必须换算成百分比单位。
  2. 各个属性中如果使用百分比,相对父元素的属性并不是唯一的。比如width和height相对于父元素的width和height,而margin、padding不管垂直还是水平方向都相对比父元素的宽度、border-radius则是相对于元素自身等等,造成我们使用百分比单位容易使布局问题变得复杂。
5、移动端适配 1px 的问题

产生原因

主要是跟一个东西有关,DPR(devicePixelRatio) 设备像素比,它是默认缩放为100%的情况下,设备像素和CSS像素的比值 window.devicePixelRatio=物理像素 /CSS像素

目前主流的屏幕DPR=2 (iPhone 8),或者3 (iPhone 8 Plus)。拿2倍屏来说,设备的物理像素要实现1像素,而DPR=2,所以css 像素只能是 0.5。一般设计稿是按照750来设计的,它上面的1px是以750来参照的,而我们写css样式是以设备375为参照的,所以我们应该写的0.5px就好了啊! 试过了就知道,iOS 8+系统支持,安卓系统不支持

解决方案

  1. WWDC对iOS统给出的方案 推荐指数:**

在 WWDC大会上,给出来了1px方案,当写 0.5px的时候,就会显示一个物理像素宽度的 border,而不是一个css像素的 border。 所以在iOS下,你可以这样写

border:0.5px solid #E5E5E5

可能你会问为什么在3倍屏下,不是0.3333px 这样的?经过我测试,在Chrome上模拟iPhone 8Plus,发现小于0.46px的时候是显示不出来

优点: 简单,没有副作用
缺点: 支持iOS 8+,不支持安卓。后期安卓follow就好了
2. 使用边框图片推荐指数:**

  border: 1px solid transparent;
  border-image: url('./../../image/96.jpg') 2 repeat;

优点: 没有副作用
缺点: border颜色变了就得重新制作图片;圆角会比较模糊
3. 使用box-shadow实现 推荐指数:***

box-shadow: 0  -1px 1px -1px #e5e5e5,   //上边线
            1px  0  1px -1px #e5e5e5,   //右边线
            0  1px  1px -1px #e5e5e5,   //下边线
            -1px 0  1px -1px #e5e5e5;   //左边线

前面两个值 x,y 主要控制显示哪条边,后面两值控制的是阴影半径、扩展半径
优点:使用简单,圆角也可以实现
缺点:模拟的实现方法,仔细看谁看不出来这是阴影不是边框
4. 使用伪元素 推荐指数:****

1 条border

.setOnePx{
  position: relative;
  &::after{
    position: absolute;
    content: '';
    background-color: #e5e5e5;
    display: block;
    width: 100%;
    height: 1px; /\*no\*/
    transform: scale(1, 0.5);
    top: 0;
    left: 0;
  }
}

可以看到,将伪元素设置绝对定位,并且和父元素的左上角对齐,将width 设置100%,height设置为1px,然后进行在Y方向缩小0.5倍

4 条border

.setBorderAll{
     position: relative;
       &:after{
           content:" ";
           position:absolute;
           top: 0;
           left: 0;
           width: 200%;
           height: 200%;
           transform: scale(0.5);
           transform-origin: left top;
           box-sizing: border-box;
           border: 1px solid #E5E5E5;
           border-radius: 4px;
      }
    }

同样为伪元素设置绝对定位,并且和父元素左上角对其。将伪元素的长和宽先放大2倍,然后再设置一个边框,以左上角为中心,缩放到原来的0.5倍

优点:全机型兼容,实现了真正的1px,而且可以圆角
缺点:暂用了after 伪元素,可能影响清除浮动
5. 设置viewport的scale值 推荐指数:*****
这个解决方案是利用viewport+rem+js 实现的

<html>
  <head>
      <title>1px question</title>
      <meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
      <meta name="viewport" id="WebViewport" content="initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">        
      <style>
          html {
              font-size: 1px;
          }            
          \* {
              padding: 0;
              margin: 0;
          }
          .top_b {
              border-bottom: 1px solid #E5E5E5;
          }

          .a,.b {
                      box-sizing: border-box;
              margin-top: 1rem;
              padding: 1rem;                
              font-size: 1.4rem;
          }

          .a {
              width: 100%;
          }

          .b {
              background: #f5f5f5;
              width: 100%;
          }
      </style>
      <script>
          var viewport = document.querySelector("meta[name=viewport]");
          //下面是根据设备像素设置viewport
          if (window.devicePixelRatio == 1) {
              viewport.setAttribute('content', 'width=device-width,initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no');
          }
          if (window.devicePixelRatio == 2) {
              viewport.setAttribute('content', 'width=device-width,initial-scale=0.5, maximum-scale=0.5, minimum-scale=0.5, user-scalable=no');
          }
          if (window.devicePixelRatio == 3) {
              viewport.setAttribute('content', 'width=device-width,initial-scale=0.3333333333333333, maximum-scale=0.3333333333333333, minimum-scale=0.3333333333333333, user-scalable=no');
          }
          var docEl = document.documentElement;
          var fontsize = 32\* (docEl.clientWidth / 750) + 'px';
          docEl.style.fontSize = fontsize;
      </script>
  </head>
  <body>
      <div class="top\_b a">下面的底边宽度是虚拟1像素的</div>
      <div class="b">上面的边框宽度是虚拟1像素的</div>
  </body>
</html>

优点:全机型兼容,直接写1px不能再方便
缺点:适用于新的项目,老项目可能改动大

参考:https://juejin.cn/post/6844903877947424782#heading-16

6、toB 和 toC 项目的区别

to B(business)即面向企业,to C( customer)即面向普通用户

toC 产品更注重产品用户的共性而淡化角色关系,而 toB 产品则更强调面向用户、客户的角色关系,而淡化共性提取。实际上,这是由服务对象所引起的,C 端产品的服务对象,由终端所限,是一个面向个体的服务。而 B 端服务使用最终是面向一个系统体系组织,在干系人间配合使用中发挥产品价值。
一个好的产品 toB 可以让组织的系统变得更好,最终反哺于系统中的各个单位。需求动力之不同 toC 的产品方法论,用户体验是几乎最为重要的需求来源,腾讯此前,也以"以用户体验为归依"来驱动企业产品打造。

但 B 端产品则不同,B 端在一个商业的背景之下,B 端的决策思路是,“以企业获益为归依”,系统是否有利于企业的生产力,竞争力等,单纯的用户体验,仅能让员工得到片刻的享受, 但无法说服企业,企业并不会为一个不能"赚钱"的东西买单。需求动力的不同,引发的这是购买使用决策体系的变化。

toB 产品应更考虑 获益与系统性价值,部分情况还有可能会牺牲掉局部个体的利益,对于使用者而言应该是自律或他律的,toC 产品则更考虑的是个体用户的偏好,并长时间内,基于技术效率的提升,产品的服务中心更多地围绕着更高效地帮助用户的"欲望"释放进行设计,对于使用者而言是一个释放自我的存在。

7、移动端兼容性
1. 时间格式化后,在ios手机展示错误(展示NaN或null)

原因:ios无法识别 - 格式的日期,例如:2022-01-13
解决:使用replace将 - 替换成 /

let newTime = oldTime.replace(/-/g, "/")

2. 设置圆角(border-radius:50%;)部分手机显示为椭圆

原因:使用rem做屏幕适配会出现这个问题;,rem在换算为px时,会是一个带小数点的值,安卓对小于1px的做了处理(不同浏览器对小于1px的处理方式不同,有的采用四舍五入,有的大于某个值展示1px否则就舍去),从而导致圆角不圆;在ios下就没有这个问题

解决
1、 先把width,height的值放大一倍,然后用transform scale(.5)缩小一倍,接着用transform-origin调整下圆的位置就大功告成了

 i{
       display inline-block
       width .16rem
       height .16rem
       background-color #D0021B
       border-radius 50%
       transform scale(.5)
       transform-origin: 0% center
   }

2、设置圆角时,设置具体的数据,不用百分比的形式

3. 安卓手机line-height和height相等,文案垂直不居中

原因:推测可能是Android在排版计算的时候参考了primyfont字体的相关属性(即HHead Ascent、HHead Descent等),而primyfont的查找是看font-family里哪个字体在fonts.xml里第一个匹配上,而原生Android下中文字体是没有family name的,导致匹配上的始终不是中文字体,所以解决这个问题就要在font-family里显式申明中文,或者通过什么方法保证所有字符都fallback到中文字体

解决:设置字体为系统字体,在不是要求一定使用特殊字体的情况下可以参考以下字体的设置

// 通用设置
body {
  font-family: system-ui, —apple-system, Segoe UI, Roboto, Emoji, Helvetica, Arial, sans-serif;
}

// emoji字体
@font-face {
  font-family: Emoji;
  src: local("Apple Color Emojiji"), local("Segoe UI Emoji"), local("Segoe UI Symbol"), local("Noto Color Emoji");
  unicode-range: U+1F000-1F644, U+203C-3299;
}

// 衬线字体
.font-serif {
  font-family: Georgia, Cambria, "Times New Roman", Times, serif;
}

// 等宽字体
.font-mono {
  font-family: Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
}

4. 安卓手机部分版本input的placeholder偏上

原因:未知
解决:设置input的line-height为normal

input {
    line-height: normal
}

5. 输入框在页面较底部时,安卓手机弹出的键盘会遮挡且点击原生键盘的关闭按钮收回键盘时,输入框没有失焦

原因:未知

解决
Element.scrollIntoView()和Element.scrollIntoViewIfNeeded()方法让当前的元素滚动到浏览器窗口的可视区域内。
使用Element.scrollIntoView()和Element.scrollIntoViewIfNeeded()解决遮挡问题,监听输入框聚焦事件,调用上面的方法将激活的元素(输入框)滚动到可视区域
通过监听页面resize事件来解决点击原生键盘的关闭按钮收回键盘时,输入框没有失焦的问题
可参考以下代码(使用vue实现)

data() {
    return {
        originHeight: 0,
        isAndroid:
            /Android/gi.test(navigator.userAgent) ||
            /adr/gi.test(navigator.userAgent),
        resizeTimer: null
    };
},
methods: {
    resizeFn() {
        // 防止部分手机触发两次resize事件导致无法拉起键盘
        if (this.resizeTimer) return;
        this.resizeTimer = setTimeout(() => {
            let resizeHeight =
                document.documentElement.clientHeight ||
                document.body.clientHeight;
            if (this.originHeight > resizeHeight) {
                // 拉起键盘会有动画,所以需要加延时,否则不管用
                setTimeout(() => {
                    if ("scrollIntoView" in document.activeElement) {
                        document.activeElement.scrollIntoView();
                    } else {
                        document.activeElement.scrollIntoViewIfNeeded();
                    }
                }, 0);
            } else {
                document.activeElement.blur();
                document.removeEventListener("resize", this.resizeFn);
            }
            clearTimeout(this.resizeTimer);
        }, 100);
    }
},
mounted() {
    if (this.isAndroid) {
        this.originHeight =
            document.documentElement.clientHeight ||
            document.body.clientHeight;
        window.addEventListener("resize", this.resizeFn);
    }
}

6. ios手机父元素设置了overflow:hidden和border-radius,子元素超出部分不隐藏

原因:未知
解决:在父元素加transform: rotate(0deg)属性

.father {
    transform: rotate(0deg)
}

7. ios手机页面滚动时动画停止

原因:iOS的事件处理机制有关,iOS最先响应屏幕反应。响应顺序依次为Touch——Media——Service——Core架构,当用户只要触摸接触了屏幕之后,系统就会最优先去处理屏幕显示也就是Touch这个层级,然后才是媒体(Media),服务(Service)以及Core架构。所以说,当系统接收到Touch事件之后会优先响应,此时会暂停屏幕上包括js、css的渲染。这个时候不光是css动画不动了,哪怕页面没有加载完如果你手指头还停留在屏幕上那么页面也不会继续加载,直到你的手松开

解决:给动画元素设置transform: translate3d(0, 0, 0);

.animation {
    transform: translate3d(0, 0, 0);
}

8. ios手机刘海屏和底部小黑条适配

原因:苹果公司提出的安全区域概念(safe area),简单的说就是我们的移动端页面可操作区域应该避开刘海区域和小黑条,因为在这两处地方的操作是不会响应我们的页面,即如果我们的按钮在这两块区域范围,那我们的点击就不会触发按钮上的事件

解决:官方给出的适配方案 iOS11同时新增了一个特性,constant(safe-area-inset-*),这是Webkit的一个CSS函数,用于获取安全区域与边界的距离,有四个预定义的变量(单位px):
safe-area-inset-left:安全区域距离左边界距离,横屏时适配
safe-area-inset-right:安全区域距离右边界距离,横屏时适配
safe-area-inset-top:安全区域距离顶部边界距离,竖屏下刘海屏为44px,iphone6系列20px,竖屏刘海适配关键
safe-area-inset-bottom:安全区域距离底部边界距离,竖屏下为34px,竖屏小黑条适配关键
一般使用 safe-area-inset-top,safe-area-inset-bottom

// 让页面占满全屏
<meta name="viewport" content="viewport-fil=cover">

// 使用@supports查询机型是否支持constant()或env()实现兼容代码隔离,个别安卓也会成功进入这个判断,因此加上-webkit-overflow-scrolling: touch的判断可以有效规避安卓机。
env() 是为了防止大于IOS11版本不支持constant()

@supports ((height: constant(safe-area-inset-top)) or (height: env(safe-area-inset-top))) and (-webkit-overflow-scrolling: touch) {
.fullscreen {
    /\* 适配齐刘海 \*/
    padding-top: constant(safe-area-inset-top);
    padding-top: env(safe-area-inset-top);
    
    /\* 适配底部小黑条 \*/
    padding-bottom: costant(safe-area-inset-bottom);
    padding-bottom: env(safe-area-inset-bottom);
  }
}

9. ios手机滚动卡顿

原因:未知
解决:给滚动的区域设置-webkit-overflow-scrolling: touch属性

.scroll {
    overflow: scroll;
    -webkit-overflow-scrolling: touch;
}

10. ios手机快速滚动页面卡死

原因:未知
解决:使用better-scroll插件做页面的滚动

11. ios手机最后一个子元素设置margin-bottom无效

原因:未知
解决:将margin-bottom改成padding-bottom

12. ios手机上输入框无法选中聚焦

原因:手贱设置了全部元素无法被选中(需求上不允许用户复制图片和文字)

\* {
    webkit-user-select: none;
}

解决
输入框元素不设置这个属性,如果还存在问题,尝试在输入框和其父元素设置-webkit-user-select:text !important属性

13. ios系统14以上,设置特殊字体(UI提供的用在数字上的字体),钱币符号和数字设置相同的颜色,页面显示的颜色不一样

原因:字体原因
解决
1.使用系统字体
2.设置颜色时透明度设置为0.99

p {
    color: rgba(#B96E16, 0.99);
}

14. ios系统14以上,使用transform实现动画时,会出现闪屏的情况

原因:未知
解决:在做动画的元素的父元素设置transform: translate3d(0, 0, 1); backface-visibility: hidden;

.father {
    transform: translate3d(0, 0, 1);
    backface-visibility: hidden;
}

15. ios手机,图片使用transform rotateY()不显示的问题

原因:猜测是ios手机又视角概念,使用transform rotateY()旋转后,视角出现问题
解决:在父元素增加perspective: 1;

.father {
    perspective: 1;
}

16. transform导致z-index失效

原因:元素样式包含transform时形成新的堆叠上下文
解决
1.父级,任意父级,非body级别,设置overflow:hidden可恢复和其他浏览器一样的渲染。
2.元素设置transform: translateZ(120px)

17. 使用border-image后,border-radius无效

原因:未知
解决:设置父级元素背景渐变,设置padding,元素覆盖在上面营造成渐变边框的效果

18. 块级元素内嵌套图片,图片上下不居中

原因:未知
解决
1.在div内设置font-size和行高为0,使用flex布局居中
2.设置图片diaplay:block

19. 多个子元素设置为圆形时(测试结果为超过3个),或出现部分呈现椭圆形

原因:未知
解决:宽高等属性设置为两倍,然后缩放0.5

20. webview 前端上传图片被旋转

背景:用户上传图片,前端将这张图片绘制到canvas画布上
问题:绘制在canvas上的图片出现旋转(ios版本大于等于13.4.1的手机不需要前端对其调整图片方向,无论倒着拍,还是旋转拍,图片上传后的方向都是正确的,所以需要对ios的版本进行判断)
原因:在手机中默认横排才是正确的拍照姿势,如果我们手机竖着拿然后逆时针旋转90°这才是正确的拍照姿势,这时候拍出来的照片展示在canvas中是不会被旋转的。如果以其他角度拍搜时,就会发生旋转
解决

function imgToCanvasWithOrientation(img, width, height, orientation) {
    let canvas = document.createElement("canvas");
    let ctx = canvas.getContext("2d");
    canvas.width=width
    canvas.height=height
    if (判断机型系统不高于ios13.4.1) {
        switch (orientation) {
            case 3:
                ctx.rotate(Math.PI)
                ctx.drawImage(img, -width, -height, width, height);
                break;
            case 6:
                ctx.rotate(0.5 \* Math.PI)
                ctx.drawImage(img, 0, -height, width, height);
                break;
            case 8:
                ctx.rotate(3 \* Math.PI / 2)
                ctx.drawImage(img, -width, 0, width, height);
                break;
            default:
                ctx.drawImage(img, 0, 0, width, height);
                break
        }
    }
    
    return canvas;
}

21. 浏览器置于后台(移动端表现为去聊微信了),倒计时不准问题

原因:浏览器的“休眠”模式,页面未激活状态时,浏览器为节省性能,会停止或减少定时任务
解决:使用visibilitychange监听页面是否可见(激活)去重新拉取后台时间,使用setTimeout去进行倒计时,setTimeout会有误差,每次执行需要计算出减去误差后的时间作为下次执行的间隔

22. ios手机上input输入框设置opacity会导致聚焦拉起键盘时,不会把输入框挤到可视区域

原因:设置opacity小于等于0.01时,就会出现这个问题
解决:设置opacity大于0.01

.box {
    opacity:0.011
}

23. 移动端300ms延迟问题

原因:历史包袱问题:以前网站都是为大屏幕电脑设计的,手机上预览就会导致内容被缩小了,为解决这个问题,就约定双击屏幕就将网页等比例放大缩小,如何判断用户是否是双击了呢?那就在首次点击后等待300毫秒,判断用户是否再次点击了屏幕,点击了就判断是双击。这也是会有上述 300 毫秒延迟的主要原因
解决
1.在HTML文档头部添加如下meta标签,添加了user-scalable=no会禁止缩放

<meta name="viewport" content="width=device-width,user-scalable=no">

2.CSS touch-action属性

html {
    touch-action: none
}

24. 设置元素溢出隐藏时(overflow:hidden),部分安卓手机会把文字的头部切掉

原因:因为使用了rem的原因
解决:给元素设置line-height的值大于设置的font-size的值

.test {
  font-size: 20px;
  line-height: 24px
}

25. vite3创建的vue项目本地开发服务在ios12.1及以下版本的手机上白屏

原因:globalThis为undefined的原因
解决:给在入口的html文件将globalThis指向window

<script>
  if (globalThis === undefined) { var globalThis = window; }
</script>

26. ios手机上将图片转成base64失败

原因:转换需要给图片设置允许跨域,但是在ios手机上允许跨域和给src赋值有顺序的区别,在chrome模拟没顺序问题
解决:先给Image对象设置允许跨域,再给Image对象的src赋值
在这里插入图片描述

27. vite 创建的项目使用可选链操作符(?.)本地启动服务在 ios13.4 以下版本的手机白屏报错

原因:vite 在启动本地服务时,只处理语法转译不包含 polyfill,可选链操作符语法在 ios13.4 以下版本不支持,所以会报错白屏
解决:使用rollup-plugin-esbuild将可选链操作符语法降级到兼容低版本浏览器。如果是生产环境使用*@vitejs/plugin-legacy*

npm install rollup-plugin-esbuild -D

// vite.config.ts
import vue from "@vitejs/plugin-vue";
import legacy from "@vitejs/plugin-legacy";
import esbuild from "rollup-plugin-esbuild";

export default defineConfig(({ command }) => {
    const plugins = [vue()];
    if (command === "build") {
        plugins.push(
            legacy({
                targets: ["Android >= 8", "iOS >= 10"],
            })
        );
    }
    if (command === "serve") {
        plugins.push(
            esbuild({
                target: "ios12",
                loaders: { ".vue": "js", ".ts": "js" },
            })
        );
    }
    return {
        plugins,
    };
});

28. h5输入框被遮挡的解决方案

1、安卓手机里面由于在调出键盘的时候整个body高就是可视区的高,只需要fixed或者position为bottom:0就可以,
2、iphone手机在调出键盘的时候body的高度始终不变,在设备信息os 后面的版本号低于11的手机里面可以在聚焦以后把 document.body.scrollTop = document.body.scrollHeight; 放在定时器里面刷,失焦时解除定时器,版本号高于11的版本不需要做这一步document.body.scrollTop = document.body.scrollHeight

参考https://juejin.cn/post/7103835385280593957#heading-0

8、2X 图 3X 图适配
  1. 第一种方法 通过 css 的 DevicePixelRatio 媒体查询属性:
/\*默认大小\*/
.photo {background-image: url(image100.png);}
/\* 如果设备像素大于等于2,则用2倍图 \*/
@media screen and (-webkit-min-device-pixel-ratio: 2),
screen and (min--moz-device-pixel-ratio: 2) {
  .photo {
    background-image: url(image200.png);
    background-size: 100px 100px;
  }
}
/\* 如果设备像素大于等于3,则用3倍图 \*/
@media screen and (-webkit-min-device-pixel-ratio: 3),
screen and (min--moz-device-pixel-ratio: 3) {
  .photo {
    background-image: url(image300.png);
    background-size: 100px 100px;
  }
}
.photo {width:100px;height:100px;}

  1. 第二种方法通过 scss 的mixin动态修改背景图片,判断设备dpr
@mixin bg-image($url) {
    background-image: url($url + "@2x.png");
    @media (-webkit-min-device-pixel-ratio: 3),(min-device-pixel-ratio: 3) {
        background-image: url($url + "@3x.png");
    }
}
.div{
  width:30px;
  height:20px;
  background-size:30px  20px;
  background-repeat:no-repeat;
  //在这里相当于调用了上面媒体查询的方法 ,传入图片url
  @include bg-image('special\_1');     
}	```

  1. 第三种方法也是推荐的方法,通过img标签属性动态切换url:
< img width="100" height="100" src="image100.png" srcset="image200.png 2x,image300.png 3x"/>

浏览器会通过 srcset 属性来自动选择2X,3X图,比如用 iPhone 6s Plus,就会自动选择3x 的图像。

参考https://blog.csdn.net/azhou820567753/article/details/100232334

9、如何解决Android浏览器查看背景图片模糊的问题

用二倍图

这个问题是 devicePixelRatio 的不同导致的,因为手机分辨率太小,如果按照分辨率来显示网页,字会非常小,所以苹果系统当初就把 iPhone 4的960x640 像素的分辨率在网页里更改为480x320像素,这样 devicePixelRatio=2。而Android 的 devicePixelRatio比较乱,值有 1.5、2和3。为了在手机里更为清晰地显示图片,必须使用 2 倍宽高的背景图来代替 img标签(一般情况下都使用 2倍 )。

例如一个 div 的宽高是100px 100px,背景图必须是200px200px,然后设置 background-size:contain 样式,显示出来的图片就比较清晰了

10、click 的 300ms 延迟问题和点击穿透问题

方案一:禁用缩放

<meta name="viewport" content="user-scalable=no">
<meta name="viewport" content="initial-scale=1,maximum-scale=1">

表明这个页面是不可缩放的,那双击缩放的功能就没有意义了,此时浏览器可以禁用默认的双击缩放行为并且去掉300ms的点击延迟。
这个方案有一个缺点,就是必须通过完全禁用缩放来达到去掉点击延迟的目的,然而完全禁用缩放并不是我们的初衷,我们只是想禁掉默认的双击缩放行为,这样就不用等待300ms来判断当前操作是否是双击。但是通常情况下,我们还是希望页面能通过双指缩放来进行缩放操作,比如放大一张图片,放大一段很小的文字

方案二:更改默认的视口宽度

<meta name="viewport" content="width=device-width">

因为双击缩放主要是用来改善桌面站点在移动端浏览体验的,而随着响应式设计的普及,很多站点都已经对移动端坐过适配和优化了,这个时候就不需要双击缩放了,如果能够识别出一个网站是响应式的网站,那么移动端浏览器就可以自动禁掉默认的双击缩放行为并且去掉300ms的点击延迟。如果设置了上述meta标签,那浏览器就可以认为该网站已经对移动端做过了适配和优化,就无需双击缩放操作了。
这个方案相比方案一的好处在于,它没有完全禁用缩放,而只是禁用了浏览器默认的双击缩放行为,但用户仍然可以通过双指缩放操作来缩放页面。

方案三:CSS touch-action
跟300ms点击延迟相关的,是touch-action这个CSS属性。这个属性指定了相应元素上能够触发的用户代理(也就是浏览器)的默认行为。如果将该属性值设置为touch-action: none,那么表示在该元素上的操作不会触发用户代理的任何默认行为,就无需进行300ms的延迟判断。

方案四:FastClick
FastClick 是 FT Labs 专门为解决移动端浏览器 300 毫秒点击延迟问题所开发的一个轻量级的库。FastClick的实现原理是在检测到touchend事件的时候,会通过DOM自定义事件立即出发模拟一个click事件,并把浏览器在300ms之后的click事件阻止掉

点击穿透问题

  1. 使用touchstart
    第一:touchstart是手指触摸屏幕就触发,有时候用户只是想滑动屏幕,却触发了touchstart事件,这不是我们想要的结果;
    第二:使用touchstart事件在某些场景下可能会出现点击穿透的现象。
  2. 在vue中可以直接使用click.stop,js的解决办法是,直接在事件的方法中添加event.stopPropagation()
11、 phone 及 ipad 下输入框默认内阴影

刷面试题

刷题的重要性,不用多说。对于应届生或工作年限不长的人来说,刷面试题一方面能够尽可能地快速自己对某个技术点的理解,另一方面在面试时,有一定几率被问到相同或相似题,另外或多或少也能够为自己面试增加一些自信心,可见适当的刷题是很有必要的。

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

  • 前端字节跳动真题解析

  • 【269页】前端大厂面试题宝典

最后平时要进行自我分析与评价,做好职业规划,不断摸索,提高自己的编程能力和抽象思维能力。大厂面试远没有我们想的那么困难,摆好心态,做好准备,你也可以的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值