JS 面试题

183 阅读5分钟

0111

  • 事件和事件流
捕获阶段:当事件发生的时候,将事件从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首页加载速度慢?

  • 公用的JS用标签外部引入,减少app.bundel的体积,让浏览器并行下载资源文件,提高下载速度
  • 路由懒加载
  • 首页加一个loading

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 在修改数据后,视图不会立刻更新,而是等同一事件循环中的所有数据变化完成之后,再统一进行视图更新。