web面试题搜集

109 阅读7分钟

0.HTML

  • 1.lable 标签的用法
    • label扩大可点击的范围
demo1:实现点击“密码”光标也能锁定输入框

方法1:推荐!!!
<label>密码: <input type="password" /></label>

方法2:
<lable for ="mima">密码:</lable> 
<input type="password" id="mima" />

1.css

  • 1.BFC

blog.csdn.net/sinat_36422…

2.js

一、垃圾回收的必要性
要回收不用的内存,就有了垃圾回收机制
var a = "before";
var b = "override a";
var a = b; //重写a
这段代码运行之后,“before”这个字符串失去了引用(之前是被a引用)
系统检测到这个事实之后,就会释放该字符串的存储空间以便这些空间可以被再利用。

二、垃圾回收原理浅析
现在各大浏览器通常用采用的垃圾回收有两种方法:标记清除、引用计数。
1、标记清除 (目前主流的方式)
当变量进入执行环境就给标记,之后用到的时候就清除标记,不用的时候再次标记
最后。垃圾收集器完成内存清除工作,销毁那些带标记的值,并回收他们所占用的内存空间。
2、引用计数
引用就+1 不用就-1   但是循环引用的话就无法回收了  内存泄漏了

三、减少JavaScript中的垃圾回收(实现变量的复用)
1、对象object优化
// 删除obj对象的所有属性,高效的将obj转化为一个崭新的对象!
cr.wipe = function (obj) {
    for (var p in obj) {
         if (obj.hasOwnProperty(p))
            delete obj[p];
    }
};
有些时候,你可以使用cr.wipe(obj)方法清理对象,再为obj添加新的属性,就可以达到重复利用对象的目的。
虽然通过清空一个对象来获取“新对象”的做法,比简单的通过{}来创建对象要耗时一些,但是在实时性要求很高的代码中,这一点短暂的时间消耗,这是非常值得的!

2、数组array优化
清空数组 arr = [] 这种方式又创建了一个新的空对象,并且将原来的数组对象变成了一小片内存垃圾!
推荐 arr.length = 0 也能达到清空数组的目的,并且同时能实现数组重用,减少内存垃圾的产生。








    1. 递归的方式写1到100求和?
function add(num1,num2){
	var num = num1+num2;
        if(num2+1>100){
	 return num;
	}else{
	  return add(num,num2+1)
        }
 }
var sum =add(1,2); 
--------------------------------------------
			add0(num1,num2){
				let sum = 0
				for (var i = num1; i <= num2; i++) {
					sum += i
				}
				return sum
			},
--------------------------------------------
 偶数和
      function sum (){
        var he = 0;
        for( var k = 1; k <= 100; k++){
            if( k % 2 ==0 ){
                he += k;
            }
        }
        console.log("熟悉的偶数和"+ he);
    }
    sum();
    1. 事件和事件流
捕获阶段:当事件发生的时候,将事件从window依次往子元素传递
目标阶段:确定事件目标
冒泡阶段:事件目标开始处理事件,处理完以后会将事件依次传递给父元素,一直到window
// addEventListener 观察者模式原理  按钮添加事件之后 浏览器会自动触发emit() 下面模拟下原理
      		document.addEventListener('lala',()=>{
				
			})
			// 观察者模式demo 
            // 原理就是 观察者是一个对象,add事件之后会扔到对象一个属性身上,这个属性是一个数组,存储的是添加的函数;
            // 之后emit的时候回遍历这个数组去执行里面存储的函数
			let qyEvent = {
				event:{
					
				},
                		// 添加
				addEvent(eventName,fn){
					if(this.event[eventName] == undefined){
						this.event[eventName] = []
					}
					this.event[eventName].push(fn)
				},
               			 // 删除
				removeEvent(eventName,fn){
					this.event[eventName].forEach((item,i)=>{
						if(fn == item){
							this.event[eventName].splice(i,1)
						}
					})
				},
                		// 触发
				emit(eventName){
					this.event[eventName].forEach((item)=>{
						item()
					})
				}
			}
            // 定义一个事件
			let abc = ()=>{
				console.log('事件1')
			}
			// 使用
			qyEvent.addEvent('shijian1',abc) // 添加abc事件
			qyEvent.addEvent('shijian1',()=>{
				console.log('事件2')
			})
			qyEvent.removeEvent('shijian1',abc) // 删除abc事件
			// 执行  (浏览器会自动执行  这里我们需要手动执行)
			qyEvent.emit('shijian1')
            // 执行时候 事件1 就不会输出 已经移除了

1.深拷贝 浅拷贝

  • 浅拷贝的意思就是只复制引用,而未复制真正的值。
  • 深拷贝就是对目标的完全拷贝。它们老死不相往来,谁也不会影响谁。
    • 实现深拷贝主要是两种:
      • 1.利用 JSON 对象中的 parse 和 stringify
      • 2.利用递归来实现每一层都重新创建对象并赋值
const originArray = [1,2,3,4,5];
const originObj = {a:'a',b:'b',c:[1,2,3],d:{dd:'dd'}};

const cloneArray = originArray;
const cloneObj = originObj;

cloneArray.push(6);
cloneObj.a = {aa:'aa'};

console.log(cloneArray); // [1,2,3,4,5,6]
console.log(originArray); // [1,2,3,4,5,6]

console.log(cloneObj); // {a:{aa:'aa'},b:'b',c:Array[3],d:{dd:'dd'}}
console.log(originArray); // {a:{aa:'aa'},b:'b',c:Array[3],d:{dd:'dd'}}

上面的代码是最简单的利用 = 赋值操作符实现了一个浅拷贝,
随着 cloneArray 和 cloneObj 改变,originArray 和 originObj 也随着发生了变化。

JSON.parse(JSON.stringify(originArray))

  • JSON.stringify 对象 => 字符串。
  • JSON.parse 字符串 => 对象。
const originArray = [1,2,3,4,5];
const cloneArray = JSON.parse(JSON.stringify(originArray));
console.log(cloneArray === originArray); // false

const originObj = {a:'a',b:'b',c:[1,2,3],d:{dd:'dd'}};
const cloneObj = JSON.parse(JSON.stringify(originObj));
console.log(cloneObj === originObj); // false
改变副本 不会 影响母本
确实是深拷贝,也很方便。但是,这个方法只能适用于一些简单的情况
(如果对象中有函数 就不行了)

const originObj = {
  name:'axuebin',
  sayHello:function(){
    console.log('Hello World');
  }
}
console.log(originObj); // {name: "axuebin", sayHello: ƒ}
const cloneObj = JSON.parse(JSON.stringify(originObj));
console.log(cloneObj); // {name: "axuebin"}

发现在 cloneObj 中,有属性丢失了。。。那是为什么呢?
函数 会在转换过程中被忽略。。。

递归的方法

function deepClone(source){
  const targetObj = source.constructor === Array ? [] : {}; // 判断复制的目标是数组还是对象
  for(let keys in source){ // 遍历目标
    if(source.hasOwnProperty(keys)){
      if(source[keys] && typeof source[keys] === 'object'){ // 如果值是对象,就递归一下
        targetObj[keys] = source[keys].constructor === Array ? [] : {};
        targetObj[keys] = deepClone(source[keys]);
      }else{ // 如果不是,就直接赋值
        targetObj[keys] = source[keys];
      }
    } 
  }
  return targetObj;
}

2.原型 原型链

  • 原型对象(prototype)
    • 所有的函数都有 prototype 而且是函数特有的
    • 上面的所有方法和属性都能被函数new出来的实例对象访问
      • 因为对象身上有 proto 指向 原型(prototype)
	function Star(uname, age) {
         this.uname = uname;
         this.age = age;
      }
      Star.prototype.sing = function() {
         console.log('我会唱歌');
      }
      var ldh = new Star('刘德华', 18);
      // 1. 只要是对象就有__proto__ 原型, 指向原型对象 prototype
      console.log(ldh.__proto__ === Star.prototype); // true
      prototype 也是一个对象 里面有属性和方法 还有 __proto__ 而且有一个构造器指向构造函数本身
  • 构造器 (constructor)
    • 构造器存在于 函数的 prototype 中,保存了指向函数的引用(指针)
      • 通俗理解就是 constructor 指向函数本身 上图的分析 打印ldh 这个实例对象
- 1.对象身上本身有uname age属性
- 2.ldh.sing() 本质是去Star.prototype 上面去查找 因为 ldh.__proto__ === Star.prototype
- 3.ldh.toString() 本质是去Star.prototype.__proto__ 上面去查找
	ldh.__proto__.__proto__  (原型链查找)
- 4.Star.prototype 中有一个构造器 constructor 指向了构造函数本身 Star
总结:prototype 只有函数或者构造函数才有的属性
	__proto__ 是任何对象都有的属性

2.1 常规面试题 如何准确判断一个数据是数组?

  • 下面三种方法 返回都是 bool 判断[ ] 是都是数组
  • 1.[ ] instanceof Array
  • 2.Object.prototype.toString.call([ ]) === '[object Array]'
  • 3.Array.isArray([ ])

3.js异步

  • 解释:JS是单线程 出现请求 定时器等耗时操作时候回扔到任务队列中,主线程任务执行完毕去任务队列中轮询,有任务就执行。

3.1 模拟

 <script>
 	
      function loadImage(src, resolve, reject) {
         let image = new Image()
         image.src = src
         image.onload = () => {
            resolve(image)
         }
         image.onerror = reject
      }
      console.log('111');
      // JS是单线程  调用这个方法 产生异步 扔到任务队列中   JS会继续执行  然后去任务队列中去轮询
      loadImage('./images/2.jpg', (image) => {
         document.body.appendChild(image)
         console.log('success');
      }, () => {
         console.log('fail');
      })
      console.log('222');
      // 最终结果是先打印 我来了   之后打印success
   </script>
      分析:虽然 loadImage 方法先调用 但是会扔到任务队列中,优先执行主线程 
      先输出 console.log('111'); 之后是 console.log('222');
      之后再去任务队列中拿回调结果

2.如何优化SPA首页加载速度慢?

分析方法:
1.大文件定位
我们可以使用webpack可视化插件Webpack Bundle Analyzer 查看工程js文件大小,然后有目的的解决过大的js文件。
npm install --save-dev webpack-bundle-analyzer
在webpack中设置如下,然后npm run dev的时候会默认在浏览器上打开
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

module.exports = {
  plugins: [
    new BundleAnalyzerPlugin()
  ]
}

  • 公用的JS用标签外部引入,减少app.bundel的体积,让浏览器并行下载资源文件,提高下载速度
  • 路由懒加载 图片懒加载
component: () => import('./components/ShowBlogs.vue')
 vuecli 3中,我们还需要多做一步工作
因为 vuecli 3默认开启 prefetch(预先加载模块),提前获取用户未来可能会访问的内容
在首屏会把这十几个路由文件,都一口气下载了
所以我们要关闭这个功能,在 vue.config.js中设置

  • 首页加一个loading
  • 移除console
  • 长列表分页加载

3.Vue生命周期

  • 1.创建前 (beforeCreate)
此时实例初始化了,但是数据观察和时间机制没完成,不能获取DOM节点
  • 2.创建后 (create)
Vue实例已经创建 但是还是不能获取DOM节点
  • 3、beforeMount
在挂载开始之前被调用:相关的render函数首次被调用。 
  • 4、mounted
  • 5、beforeUpdate
数据更新时调用
  • 6、updated
组件DOM已经更新,所以你现在可以执行依赖于DOM的操作。
  • 7、activated
keep-alive组件激活时调用。 
  • 8、deactivated
keep-alive组件停用时调用。 
  • 9、beforeDestroy
实例销毁之间调用。
  • 10、destroyed
Vue实例销毁后调用。调用后,Vue实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。

3.nexttick

  • Vue 实现响应式并不是数据发生变化之后 DOM 立即变化,而是按一定的策略进行 DOM 的更新
  • 简单来说,Vue 在修改数据后,视图不会立刻更新,而是等同一事件循环中的所有数据变化完成之后,再统一进行视图更新。