深入理解JS中的重点问题

1.模块化如何实现的,请用闭包的方式实现一个简单的module

模块化可以帮助我们抽离公共的代码,隔离作用域,避免命名冲突的问题

先看下面这个简单的代码,封装一个函数,并立即执行,函数返回两个对象字面量,我们可以在my变量访问到它们

let my = (function myModule() {
  let myName = 'default';
  function setName(name) {
    myName = name;
  }
  function getName() {
    console.log(myName);
  }
  return {
    setName,getName
  }
})()
my.setName('renye')
my.getName()

每次实例化一个myModule都是一个新的实例,它们之间不共享属性,闭包帮我们完成了局部变量myName在js内存中的驻留,不会被垃圾回收(js的垃圾回收用的是标记清除:变量进入作用域就标记,出作用域就清楚标记,每隔一段时间就执行一下jc)

 

2.this的4种绑定方式:

this有4种绑定方式:

默认绑定:我们经常使用的独立调用一个函数,这种时候,函数内部的this就是使用默认绑定,绑定到windows,严格模式下和node环境下都是undefined

隐式绑定:这也是最常见的绑定方式,当函数的调用位置有上下文对象的时候,this会隐式绑定到这个对象上,但是这种绑定方式可能会出现绑定丢失的情况:最常见的情况是发生在传入回调的时候,想一下我们在React的组件上绑定事件的时候,如果我们用的是函数声明,而不是箭头函数形式的函数表达式,那么我们绑定在JSX语法写的元素上的事件就会出现绑定this丢失的情况,因此我们在React组建的构造函数中,使用bind函数为函数绑定上了组价的this,保证事件发生时候调用的回调函数可以绑定到我们的组件上。setTimeout也有类似的回调问题

显示绑定:使用call,apply,同时bind函数可以解决绑定丢失的问题

new绑定:我们实现过new函数,被new标识符声明的函数的调用中,函数的this指向其等号左侧的对象

 

3.重绘(repaint)和重排(回流/reflow)的触发条件,vDOM如何避免重绘和重排,16.6ms这个数意味着什么,vDOM的效率一定比DOM更高吗?

要讨论重绘和重排,我们首先应该了解一下浏览器渲染页面的机制:浏览器采用Flow based Layout,也就是流式布局,这个流就是我们常说的文档流。浏览器会先解析HTML文件,生成对应的DOM树,然后解析CSS文件,生成CSSOM树,然后对这两个树进行合并,就生成了渲染树,在这颗渲染树上 ,我们知道DOM的结构和对应的样式,然后浏览器计算DOM在牙面上的大小和位置,把DOM节点渲染到页面上去。

重绘:由于DOM的color background-color等属性发生变换,不影响DOM的布局的时候,浏览器触发重绘,将改变你的属性重新渲染到页面上

重排:由于DOM的尺寸(width,height),布局(display postion float)发生改变的时候,会引起页面的重排,重排大部分时候会导致页面的重新渲染,因此我们需要优化,重排一定会重绘,重绘不一定重排

优化:

1.现代浏览器使用的一般是队列的机制来实现批量的更新,我们知道浏览器为了维持60HZ的刷新帧率,每隔至少一个16.6ms的时候可以渲染一次,但是当我们使用布局的信息,比如window对象的offsetTop,scrollTop clientTop 的时候,浏览器为了保证你可以拿到正确值,会强制去清空这个队列,保证你拿到正确的值,因此我们不能频繁访问这个属性

2.CSS优化:使用visibility:hidden代替display:none,前者只引发重绘,后者会重排,不要用css表达式,动画效果一定要用在positon为absolute和fixed上,这样动画就是重绘,要不会可能重排到怀疑人生

3.HTML优化:尽量不用table布局,table大概要花其他元素3倍以上的事件去重排,避免无意义标签,让HTML结构尽量扁平,在触发重排的时候,可以尽量影响少一些的DOM

4.vDOM:React使用了vDOM,帮助我们批量更新,vDOM的效率只有在涉及到很多DOM元素的变更的时候,效率才可能更高,简单的操作DOM,肯定是直接操作DOM的效率更高,因为vDOM最终还是要对应到真正的DOM操作,React也只是对浏览器document的API的一些封装。我们会一次性计算更多需要变更的节点,计算出变更后的结果,然后转化成真实的DOM去渲染,避免直接操作真实DOM次数过多引起过多的重绘和重排

4.常见的js设计模式:发布订阅,观察者模式,工厂模式,装饰者模式,单例模式,很多很多,选几个实际用的到的例子简单介绍一下

单例模式:弹窗组件,比如点击button弹出一个包含个人信息的弹窗,我们自然不能反复的创建不同的弹窗,而是每次弹出同一个,避免占用过多的内存,利用了闭包!这个例子特别好,重点理解

 

class CreateUser {
    constructor(name) {
        this.name = name;
        this.getName();
    }
    getName() {
         return this.name;
    }
}
// 代理实现单例模式
var ProxyMode = (function() {
    var instance = null;
    return function(name) {
        if(!instance) {
            instance = new CreateUser(name);
        }
        return instance;
    }
})();
// 测试单体模式的实例
var a = new ProxyMode("aaa");
var b = new ProxyMode("bbb");
// 因为单体模式是只实例化一次,所以下面的实例是相等的
console.log(a === b);    //true

代理模式:图片的懒加载:

var imgFunc = (function() {
  let imgNode = document.createElement('img');
  document.body.appendChild(imgNode);
  return {
    setSrc:function(src) {
      imgNode.src = src
    }
  }
})
var loadingImg = (function(){
  let img = new Image();
  img.onload = function() {
    imgFunc.setSrc(this.src)
  }
  return {
    setSrc: function(src) {
      imgFunc.setSrc('./loading.gif');
      img.src =src;
    }
  }
})()

loadingImg.setSrc('./pic.png')

中介者模式:由一个中介类统一管理状态,这可不就是我们的redux嘛~

发布订阅模式:可以说这是我们最熟悉的设计模式了,我们用的DOM的addEventListener就是这么一种模式,这里我们介绍一个Node环境下的Event订阅模块,我们实现一个简单的自己的EventEmiiter

5 class是对ES5的原型继承的语法糖,.ES6引入了的class和我们ES5前写的class有什么区别呢

首先class声明没有变量提升,class中的方法是不能使用new的,class是原型继承(Object.create(),浅拷贝)的语法糖

6.js中异步的发展,从回调到Promise到generator到async/await 它们的提出分别是为了什么,请实现一个Promise

js是单线程执行,异步对于js来讲是必不可少的。一开始我们使用的是回调函数的方式去实现异步,但是问题在于如果下一个回调函数需要的参数依赖于上一个回调函数返回的结果,在书写代码的时候,就会出现一层层的嵌套不利于阅读和书写,ES6正式提出了Promise,通过resolve和reject对应异步操作成功和失败时候的返回值,在then方法(第一个回调参数处理resolve,第二个处理reject)和catch处理错误信息,借由then方法的链式调用,我们可以写出可读性很强的代码。并且Promise还为我们提供了all race finally方法来增强我们处理多个Promise对象的方法。

generator函数:generator借鉴了协程的概念,当协程A执行到中途的时候,把程序的执行权转移给协程B,协程B执行一段时间后,再把执行权交还给A,generator函数用function* gen(){}声明,在需要暂停异步去执行的表达式和函数前,添加yield关键字,yield后面的代码会等待被yield声明的函数或表达式执行完返回结果后再执行

async/await:是对generator的封装,用async声明函数 async function asFun(){},在需要异步返回结果的的函数前添加await

7.JavaScript 中创建对象的方式有哪些,实现和区别?new和Object create

function myNew(P) {
  let o = {};
  o.__proto__ = P.prototype;
  P.prototype.constructor = P;
  P.apply(o,[].prototype.slice.call(arguments,1))
  return o;
}

//工厂方式,相当于实现了一个对象的浅拷贝
Object.prototype.myCreate = function(o) {
  let F = function() {}
  F.prototype = o;
  let newO = new F()
  return newO//新new出来的对象和传进来的o具有一样的父对象,o = F.prototype = newO.__proto__ 也就是newO.__proto__ = o
}

8.深入理解Symbol对象

//symbol的应用场景有哪些
let mySymbol1 = Symbol('renye');
let mySymbol2 = Symbol('renye');
console.log(mySymbol1 == mySymbol2);//false

let mySymbol3 = Symbol.for('renye');
let mySymbol4 = Symbol.for('renye');
console.log(mySymbol3 == mySymbol4);//true


var obj = {
  name:'ConardLi',
  [Symbol('name2')]:'renye'
}

for(let i in obj) {
  console.log(i)//name Symbol对象作为对象属性的时候不可枚举
}
//创建私有属性,利用闭包创建Symbol,利用
let Person = (function() {
  let _age = Symbol('age');
  function P(name,age) {
    this.name = name;
    this[_age] = age
  }
  P.prototype.say = function() {
    console.log (this[_age]);
  }
  return P
})()

let p1 = new Person('renye',18);
console.log(p1)//P { name: 'renye', [Symbol(age)]: 18 }

//防止XSS攻击
var REACT_ELEMENT_TYPE = (typeof Symbol === 'function' && Symbol.for && Symbol.for('react.element'))|| 0xeac7
REACT_ELEMENT_TYPE.isValidElement = function(object) {
  return typeof object === 'object' && object !== null && object.$$typeof === React_ELEMENT_TYPE
}

9.实现一个漂亮的深拷贝

10 隐式类型转换的问题

Valueof和toString :https://www.cnblogs.com/imwtr/p/4392041.html

使用 == 进行比较比较的时候,有几个重点

1.Boolean值会转成数字

2.NaN == 任何值都是false包括NaN

3.Stringh和Number比,String转成Number

 

一道经典的面试题,如何让:a == 1 && a == 2 && a == 3

//重写a对象的valueOf方法
const a = {
  num:0,
  valueOf:function() {
    return ++this.num
  }
}
let flag = a==1
flag = a==2;
flag = a==3;
console.log(flag)

11 类型检测,Object.prototype.toString.call() typeof instance of

Object.prototype.toString.call()都适用

typeof 只能检测基本类型 对于复杂类型和null都返回‘object’

p instance of P 会顺着对象的原型链去看P是否在p对象的原型链上

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章