JavaScript重点原理理解

JavaScript有一些重要的概念需要更加透彻的理解,大概讲解以下几个:

1.call,apply ,bind的使用,什么是argument ?bind的原生js实现

call和apply都是为函数绑定执行的上下文,指定一个对象来替换函数执行时候的this,它俩的区别在于call需要传入完整的参数列表,而apply可以传入一个参数数组。bing函数则是为函数绑定执行的上下文,需要传入一个this作为参数,不同于call和apply,bind不是立即执行的。下面看一下bind的实现:

//bind函数就是返回一个函数,但是函数的this绑定到指定的this上
Function.prototype.myBind = function(newThis){
  if(typeof this !== 'function') {
    throw new TypeError('need bing to a function')
  }
  let self = this;
  let arg = [].slice.call(arguments,1)
  return function() {
    self.apply(newThis,arg.concat([].slice.call(arguments)))
  }
}

argument是函数内部的参数,每个都有,可以用argument.length来访问参数的长度,但是argument不是真的数组,需要用bind里面的数组的slice方法转为真正的数组,我们可以冲argument的length实现类似函数重载的效果

2.this的显式绑定和隐式绑定还有new绑定

this在函数执行的时候,会隐式绑定到包含这个函数的对象上,this指向这个对象。显示绑定就是上面说的call apply bind,再就是new出来的函数的实例,this自动指向这个实例对象。

那什么是隐式绑定丢失呢?

function foo() {
    console.log( this.a );
}
 
var obj = {
    a: 2,
    foo: foo
};
 
var bar = obj.foo; // function reference/alias!
 
var a = "oops, global"; // `a` also property on global object
 
bar(); // "oops, global"

这种情况下,bar在调用的时候没有在对象上,就默认绑定到window,严格模式下和node环境中都是undefined。再看一个例子

var obj = {
  id: 'vexekefo',
  cool() {
    console.log(this.id);
  }
};
var id = 'someone';
obj.cool();  // vexekefo

setTimeout(obj.cool, 100); // someone

解决办法,我们可以使用箭头函数,箭头函数没有自己的this,它的this是定义它的上下文决定的

3.JavaScript的事件循环机制,宏任务和微任务,JavaScript的单线程如何理解

Js的事件循环机制:js引擎遇到一个异步事件后并不会一直等待其返回结果,而是会将这个事件挂起,继续执行执行栈中的其他任务。当一个异步事件返回结果后,js会将这个事件加入与当前执行栈不同的另一个队列,我们称之为事件队列。被放入事件队列不会立刻执行其回调,而是等待当前执行栈中的所有任务都执行完毕, 主线程处于闲置状态时,主线程会去查找事件队列是否有任务。如果有,那么主线程会从中取出排在第一位的事件,并把这个事件对应的回调放入执行栈中,然后执行其中的同步代码...,如此反复,这样就形成了一个无限的循环。这就是这个过程被称为“事件循环(Event Loop)”的原因。

分为宏任务队列,和微任务队列,宏任务是setTimeOut和setInterval,微任务是promise,node环境下还有process.nextTick,

每次从宏任务取一个,再清空微任务队列,然后进入下一轮循环

4.JavaScript的原型链,作用域分别是什么,可以用来做什么

js中的一切皆对象,每个对象都有一个prototype属性,它指向对象的原型对象,而原型对象也有prototype属性,继续指向原型对象的原型对象,最终都是Object。Object的prototype是null,我们可以通过_proto_在浏览器访问

我们可以用它做继承,在原型链上定义的函数和属性,顺着原型链往下的对象都可以访问到

作用域链:在作用域最前端的是活动对象,而最后端是全局执行环境window(浏览器宿主中);变量访问原则是,根据作用域前端往上进行搜索,如果提前搜索到变量,则停止搜索,例如上面这个例子中,name变量的值是"sub"因为其在最前端的变量对象中已经定义了,就不会往上继续检索;

5.JavaScript的继承方式主要有什么,如何实现

组合继承,原型继承,寄生组合继承,在原型链上定义可复用的函数,在构造函数内定义自己的变量。

6.JavaScript在浏览器中的事件,捕获和冒泡

浏览器事件分为三个阶段,先捕获,找到发生事件的DOM,然后处于事件发生阶段,然后冒泡,我们可以利用冒泡实现事件的委托,最常用的就是渲染了很多li标签,不必给每个li绑定onclick事件,而是在他们的父元素ul上绑定一个,如果不想让子组件的某个事件冒泡触发父组件的同名事件,可以使用stopPropagation方法

7.JavaScript的基本数据类型有哪些,基本包装类型有哪些,它们在堆栈中的存储方式是什么

基本数据类型有string number null undefined bool symbol,还有个复杂数据类型object,基本包装类型有Number String Date Object Regexp等,基本数据类型存储在栈中,定义后不可更改,而对象类型都是在栈中存储数据的引用地址,真实的数据存在堆中

8.实现对象的深拷贝和浅拷贝,它们各自应用的场景

浅拷贝是只拷贝了对象的引用地址,在堆中没有新开一块复制原有的数据,深拷贝则是会在堆中新开一块空间,完全复制一份,

深拷贝可以用转化成JSON的方式,但是只适用于Number Sring Boolean Array,就是这种可以被JSON表示的对象,其他复杂的类型则需要单独编写函数来处理

9 Javascript的闭包是什么,什么时候需要使用闭包

js的闭包:函数体内定义的函数可以拿到函数的局部变量,可以用这个封装私有属性,可以用来实现模块化,单例模式,

闭包的作用是可以用闭包访问局部变量,可以让局部变量驻留在内存中

10JavaScript模块化,不同的标准有哪些,模块化是如何实现的(联系闭包)

我们开发React的时候用的exports import是ES6的语法,开发node的时候的module.export和require是commonjs的标准。

const Helmet = require('react-helmet').default
const ReactDomServer = require('react-dom/server')
const ejs = require('ejs')
const serialize = require('serialize-javascript')
const SheetsRegistry = require('react-jss').SheetsRegistry
const colors = require('material-ui/colors')
const createMuiTheme = require('material-ui/styles').createMuiTheme
const create = require('jss').create
const preset = require('jss-preset-default').default
const asyncBootstrapper = require('react-async-bootstrapper').default

可以看到两者的不同,react-helmet用的ES6语法编写的模块,如果要在node使用,我们需要用default去拿到真正的模块实例

11 new 操作符具体做了什么

new操作符新建一个空对象,然后把构造函数的原型对象赋给新对象,然后把构造函数赋值给原型对象的constructor属性,然后

执行构造函数,把新对象传进去,参数也传进去

let New = function (P) {
        let o = {};
        let arg = Array.prototype.slice.call(arguments,1);
        
        o.__proto__ = P.prototype;
        P.prototype.constructor = P;
       
        P.apply(o,arg);
        
        return o;

12async是什么,如何使用

是ES7的异步,配合await一起使用

13箭头函数的指向

指向定义箭头函数时候的词语作用域

14事件委托机制

就是冒泡的时候子元素的事件委托给父元素执行

15.0.1+0.2 !==  0.3原因和解决办法

js中浮点数表达不精确,可以用tofix(2)+parseInt

16数组的去重,扁平化

去重:可以用new Set,直接构造

扁平化:使用reduce函数+Array.isArray+递归

17函数柯里化与惰性求值

函数柯里化我们在中间件学习的时候看过store =>next=>action => {}

这种方式可以累积参数,在需要调用的时候再调用函数,不是立即执行

18定时器为何存在不准的情况,手写一个倒计时

js定时器不准,前端重要逻辑要用后端传来的时间

19 let const var的区别

let const没有变量提升 var有变量提升 let const可以绑定到块级作用域

20 class的实现机制

class是基于ES5的function实现的,是原型继承的语法糖

21 防抖和节流函数怎么写

都是避免避免无限触发绑定在dom上的事件,一些比如scroll,输入框输入等触发频率较高的,我们不能让绑定的回调一直触发,因此有了防抖和节流两种办法。

防抖适用于输入搜索,我们不要每次input的lvalue变化我们都去搜索一次,我们在输入延迟个500ms左右再执行一次。就是一次输入后,如果500ms没有再输入,我们就执行搜索匹配

节流适用于scroll拉取网络数据,我们每过一段时间拉取一下,也不能说最后一次拉取,那中途就没有用来渲染的数据了

 

<html>
  <head></head>
  <body>
    <div style='height:400px;width:800px;line-height:400px;background-color: gray;text-align: center;font-size: 30px;'>


    </div>
    <script>  
      let num = 1;
       ele = document.querySelector('div');
      function count() {
        ele.innerHTML = num;
        num++;
      }
      ele.onmousemove = throttle(count,1000)
      // 添加防抖,在最后一次触发后再过1000ms执行,最后一次之前都不执行
      function debounce(fn,wait) {
       let timeout;
       return function() {
        if(timeout !== null) clearTimeout(timeout);
        timeout = setTimeout(fn,wait);
       }
      }
      //添加节流,减少执行次数,比如下滑拉取数据,不适合最后一次拉取,适合中途过一段时间拉取一次
      function throttle(fn,wait) {
        let preTime = Date.now();
        return function() {
          let now = Date.now();
          if(now - preTime >= wait) {
            fn();
            preTime = Date.now()
          }
        }
      }


    </script>
  </body>
</html>

 

 

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