JavaScript知识汇总

370 阅读15分钟

1. 介绍一下js的数据类型有哪些,值是如何存储的


基本数据类型,number,string,boolean,null, undefined,symbol
- 基本数据类型特点:直接存储在栈(stack)中,占据空间小、大小固定,属于被频繁使用数据,所以放入栈中存储。
引用数据类型,object
- 引用数据类型:同时存储在栈(stack)和堆(heap)中,占据空间大、大小不固定。引用数据类型在栈中存储了指针,堆中储存实体,首先检索其在栈中的地址,取得地址后从堆中获得实体。

2.JS中数据类型的判断


typeof
-   优点:能快速检查undefined,string,number,boolean类型
- 缺点:当类型为object,null,array时都会返回object,所以不能区分这三类
instanceof
- 优点:能检测array,function,object类型 
- 缺点:检测不了number,boolean,string
constructor
- 缺点:如果手动改变prototype,则检测不了
Object.prototype.toString.call()
- 优点:能准确的判断所有的类型。
- 缺点:写法过于繁琐

3.什么是js事件环?


  • 主线程从"任务队列"中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)
    • 所有任务就被分成两种: 同步,异步
    • 同步:在主线程上排队执行的任务(这里会形成一个执行栈:先进后出)
    • 异步任务: 不进入主线程,进入'任务队列'的任务,只有'任务队列'通知主线程,某个异步任务可以执行了,然后主线程执行完同步任务异步任务才会进入执行栈排队,等待被执行。
async function a1 () {
    console.log('a1 start')
    await a2()
    console.log('a1 end')
}
async function a2 () {
    console.log('a2')
}

console.log('script start')

setTimeout(() => {
    console.log('setTimeout')
}, 0)

Promise.resolve().then(() => {
    console.log('promise1')
})

a1()

let promise2 = new Promise((resolve) => {
    resolve('promise2.then')
    console.log('promise2')
})

promise2.then((res) => {
    console.log(res)
    Promise.resolve().then(() => {
        console.log('promise3')
    })
})
console.log('script end')

猜猜运行顺序是什么?

4. undefined 与 undeclared 的区别?


  • undefined 已声明未赋值(报错undefined)
  • undeclared 在作用域中未声明(报错 not defined)
  • null和undefined都是基本数据类型,typeof null => object是js一个bug

5. javascript的作用域和作用域链


  • 作用域: 作用域是定义变量的区域,它有一套访问变量的规则,这套规则来管理浏览器引擎如何在当前作用域以及嵌套的作用域中根据变量(标识符)进行变量查找。
  • 作用域链: 作用是保证对所有变量和函数的有序访问,通过作用域链,我们可以访问到外层环境的变量和函数。

6.javascript 创建对象的几种方式


1.)字面量 ,let obj = {}
2.)工厂模式 ,创建大量相似对象,但没有解决对象识别的问题,无法判断是谁的实例
3.)构造函数,所创建的对象和构造函数建立起了联系,因此我们可以通过原型来识别对象的类型,但是构造函数存在一个缺点就是,造成了不必要的函数对象的创建。因为在 js中函数也是一个对象,因此如果对象属性中如果包含函数的话,那么每次我们都会新建一个函数对象,浪费了不必要的内存空间,因为函数是所有的实例都可以通用的。
4.)原型模式,好处是所有对象实例共享它的属性和方法。解决了构造函数模式的函数对象的复用问题。这种模式也存在一些问题,一个是没有办法通过传入参数来初始化值,另一个是如果存在一个引用类型如 Array 这样的值,那么所有的实例将共享一个对象,一个实例对引用类型值的改变会影响所有的实例。
5.)混合模式(构造函数模式+原型模式),混合模式共享着对相同方法的引用,又保证了每个实例有自己的私有属性,对于代码的封装性不够好

7. 什么是原型,原型链,有什么特点?


原型对象
function My(name,age){
    this.name = name,
    this.age = age,
    this.say = function(){
      console.log('我叫'+ this.name + '今年'+this.age)
    }
  }

  My.prototype.sing = function(){
    console.log('小冤家,你干嘛,像个傻瓜')
  }

  let p1 = new My('zf','12')
  console.log(typeof p1)  //object
  console.log(typeof My)  //function
  // 每个构造函数都有一个prototype属性,这个属性是一个指针,指向一个对象,对象包含了所有实例共享的属性和方法。(该函数实例化的所有对象的__proto__属性都指向这个对象,即他是该函数所有实例的原型对象。)
  // 这个对象里有一个constructor属性,这个属性是一个指针,指向prototype所在的函数对象
  console.log(My.prototype === p1.__proto__)  //true
  console.log(My.prototype.constructor === My) //true

  // 获取实例的原型对象的方法Object.getPrototypeof(obj)
  console.log(Object.getPrototypeOf(p1) === My.prototype) //true
  console.log(p1.__proto__)
  console.log(p1.constructor.prototype)
  // for in 循环可遍历原型链上的所有属性,可以用p1.hasOwnProperty(key)方法来过滤一下
  // Object.keys(p1) //不能遍历原型上的属性,返回对象p1的自身可枚举属性组成的数组['name','age','say']
原型链
原型链
当访问一个对象的某个属性时,会先在这个对象本身属性上查找,如果没有找到,则会去它的__proto__隐式原型上查找,即它的构造函数的prototype,
如果还没有找到就会再在构造函数的prototype的__proto__中查找,这样一层一层向上查找就会形成一个链式结构,我们称为原型链。
Object.prototype 就是原型链的终点,我们打印Object.prototype.__proto__,返回的是一个null空对象

特点:
所有该构造函数的实例共享一个原型对象,当原型对象里面有引用类型的值时,一个实例对引用类型值的改变会影响所有的实例
  • 对象的__proto__链
let obj1 = {name:'zf'}
let obj2 = {age:18}
<!--指定obj1的原型对象为obj2-->
Object.setPrototypeOf(obj1,obj2) //对象obj1的__proto__指向obj2
<!--获取obj1的原型对象-->
Object.getPrototypeOf(obj1)  //打印出obj2

8. Promise


  • 可以解决的问题:
    • 并发问题(同步多个异步方法的执行结果)
    • 链式调用,解决多个回调嵌套的问题
  • promise 是一个类,一种异步流程的控制手段
    • 通过new执行,只有一个参数,叫excutor执行器,默认new时就会调用
    • excutor执行器有两个参数,resolve,reject
    • 有三种状态,pending =》 resolve成功了,reject失败了,状态不能逆转
    • 每个promise都有一个then方法
  • 手写promise的链式调用:
let Promise = require('./promise')

let p = new Promise((resolve,reject)=>{
  resolve('hello')
})

let promise2 = p.then(data=>{
  return new Promise((resolve,reject)=>{
    setTimeout(() => {
      resolve('链式调用成功了')
    }, 1000);
  });
},err=>{
  console.log(err)
})

promise2.then(data=>{
  console.log(111,data)  //链式调用成功了
},err=>{
  console.log(222,err)
})

*************************************************
const PENDING = "PENDING";
const FULFILLED = "FULFILLED";
const REJECTED = "REJECTED";

// promise的处理函数
const resolvePromise = ({promise2,x,resolve,reject})=>{
  // console.log(promise2,x,resolve,reject)
// 处理x的类型  来决定调用resolve还是reject
// 必须写的很严谨
  if(promise2 === x){ //自己等自己完成
    return reject(new TypeError('Chaining cycle detected for promise'))
  }
  // 判断X  是不是一个普通值
  if(x && ['object','function'].includes(typeof x)){
    // 可能是一个promise,用then判断是不是promise
    try{
      let then = x.then;//看有没有then方法
      if(typeof then === 'function'){
        // 是promise了
        then.call(x,y=>{ //如果是promise就采用这个promise的结果
          resolve(y);
        },r=>{
          reject(r);
        })
      }else{
        resolve(x)  //常量就直接抛出
      }
    }catch(e){
      reject(e);
    }
  }else{
    // 不是promise
    resolve(x)
  }
}

class Promise{
  constructor(executor){
    this.status = PENDING;
    this.value = undefined;
    this.reason = undefined;
    this.onResoledCallBacks = [];
    this.onRejectCallBacks = [];
    let resolve = (value)=>{
      this.value = value;
      this.status = FULFILLED;
      this.onResoledCallBacks.forEach(item=>item());
    }
    let reject = (reason)=>{
      this.status = REJECTED;
      this.reason = reason;
      this.onRejectCallBacks.forEach(item => item());
    }
    try{
      executor(resolve,reject);
    }catch(e){
      console.log(e);
    }
  }
  then(onFulfilled,onRejected){
    // then方法调用后,应该返回一个新的promise,
    let promise2 = new Promise((resolve,reject)=>{
      // 在promise2中  取到上一次的状态,来决定promise2是成功还是失败
      if(this.status === FULFILLED){
        // 当前onFulfilled  onRejected不能在当前上下文执行,为了确保代码promise2存在
        setTimeout(() => {
          try{
            // 让then中的方法执行,拿到他的返回值
            let x = onFulfilled(this.value);
            resolvePromise({promise2,x,resolve,reject});
          }catch(e){
            reject(e);
          }
        });
      }
      if(this.status === REJECTED){
        setTimeout(() => {
          try{
            let x = onRejected(this.reason);
            resolvePromise({promise2,x,resolve,reject});
          }catch(e){
            reject(e);
          }
        });
      }
      if(this.status === PENDING){
        this.onResoledCallBacks.push(()=>{
          setTimeout(() => {
            try{
              let x = onFulfilled(this.value);
              resolvePromise({promise2,x,resolve,reject});
            }catch(e){
              reject(e);
            }
          });
        })
        this.onRejectCallBacks.push(()=>{
          setTimeout(() => {
            try{
              let x = onRejected(this.reason);
              resolvePromise({promise2,x,resolve,reject});
            }catch(e){
              reject(e);
            }
          });
        })
      }
    })
    return promise2
  }
}

module.exports = Promise;

  • promise 是一种异步流程的控制手段,可以解决并发问题(同步多个异步方法的执行结果)
  • promise链式调用(先获取a,通过a再获取b),解决多个回调嵌套的问题

9. 谈谈你对this、call、apply和bind的理解


  • 在浏览器里,在全局范围内this 指向window对象;
  • 在函数中,this永远指向最后调用他的那个对象;
  • 构造函数中,this指向new出来的那个新的对象;
  • call、apply、bind中的this被强绑定在指定的那个对象上;
  • 箭头函数中this比较特殊,箭头函数this为父作用域的this,不是调用时的this.要知道前四种方式,都是调用时确定,也就是动态的,而箭头函数的this指向是静态的,声明的时候就确定了下来
  • apply、call、bind都是js给函数内置的一些API,调用他们可以为函数指定this的执行,同时也可以传参。
1.)this的显示绑定和隐式绑定
  • 1.1全局函数
函数没有明确的调用对象的时候,将对函数的this使用默认绑定:绑定到window全局对象(函数fire一直居住在window里面)
function fire(){
    console.log(this = window) //# fire是全局方法,this指向window
}
fire()  //true
  • 1.2函数内嵌套函数
function fire(){
    function innerFire(){
        console.log(this) //# 未明确调用对象,this指向window
    }
    innerFire() //# 独立函数调用
}

fire()

示例

var a = 1
console.log(a) // 1 全局变量a
console.log(this.a) // this指向window

function fire(){
    var a = 2;
    console.log(a) // 2 函数fire作用域a
    console.log(this.a)  // this指向window
    function innerFire(){
        var a = 3;
        console.log(a) // 3 函数innerFire作用域a,从自身作用域找,未找到则往上找
        console.log(this.a) // this指向window
    }
    innerFire()
}

fire() 

1.3 对象的属性值的内部函数

var obj = {
    fire:function(){
      function innerFire(){
        console.log(this) //未明确调用对象,此时this指向window
      }
      innerFire()
    }
}

obj.fire()

示例

    var a = 1;
    console.log(this.a); //this指向window
    var obj = {
        a:2,
        fire:function(){
        var a = 3;
        console.log(this.a)  // obj.fire(),obj执行了函数fire,所以this指向obj
        function innerFire(){
            var a = 4;
            console.log(this.a) // 未明确调用对象,this指向window
        }
        innerFire()
        }
    }
  obj.fire()
  • 隐式绑定:
  • 1.1 当一个函数被一个对象包含的时候,this就被隐式绑定到这个对象上了,这时候可以通过this访问对象里的其他属性。
  • 在隐式绑定下:函数只是暂时住在包含对象的旅馆里,过几天可能又要搬家了。
var obj = {
    a:'111',
    fire:function(){
      console.log(this.a) //111,this指向obj
    }
 }
  obj.fire()
  • 或者这样写
var fire = function(){
    console.log(this.a)
}

var obj = {
    a:111,
    fire:fire
}

obj.fire()
  • 1.2 动态绑定:将函数赋值给全局变量(this从宾馆搬回家了)
var obj = {
    a:1,
    fire:function(){
      console.log(this.a)
    }
 }
  var a = 2 //全局的变量a
  obj.fire()  // 1, obj执行了函数fire,所以this指向obj,
  var fireInGlobal = obj.fire; //因为fireInGlobal是全局变量,this从宾馆搬回家了
  fireInGlobal() //this指向window,输出2
var a = 2;
  var obj = {
    a:3,
    fire:function(){
      console.log(this.a)
    }
 }
  obj.fire()  //3

  function otherFire(fn){  //# otherFire是全局函数,this指向window
    fn()
 }
  otherFire(obj.fire)  //2, 
2)this的指向问题
  • 2.1 全局环境作用域:this在全局环境始终指向window
  • 2.2 函数环境作用域:函数由谁调用,this就指向谁
  • 2.2.1 非严格模式环境下
function fn(){
    console.log(this) //window
 }
  fn()
  console.log(fn === window.fn)  //true
  console.log(this === window)  //true
  console.log(fn === this.fn)  //true
2.2.2 严格模式环境下  
a 全局环境下,this指向window  
b 函数环境下,this指向undefined
  • 2.3 对象中的方法函数调用:this指向该方法所属的对象 同隐式绑定
  • 2.4 构造函数中this始终指向新对象
  • 2.5 通过事件绑定的方法: this 指向 绑定事件的对象
<button id="btn">点我</button>

oBtn.onclick = function(){
    console.log(this) //btn
}

  • 2.6 自执行函数(匿名函数): 指向全局变量window
  • 2.7 箭头函数:
  • 要点:
  • a. 箭头函数的this是在定义函数时绑定的, 不是在执行过程中绑定的
  • b. 箭头函数中的this始终指向父级对象
  • c. 所有 call() / apply() / bind() 方法对于箭头函数来说只是传入参数, 对它的 this 毫无影响。
3.) 更改this指向
  • call()
var person = {
    name:'zf',
    age:18
 }

function lzf(x,y){
    console.log(x,y)
    console.log(this)
    console.log(this.name)
}

  lzf(1,2); //this指向window---1,2---window---null
  lzf.call(person,1,2) //this指向person---1,2---person---zf
  
  • 2.apply()用法同call(),只是apply的第二个参数是数组,数组里面是函数的参数
lzf.apply(person.[1,2])
  • 3.bind(),有点不一样,他并不是改变this的指向,而是创建一个新的函数体。
var Person = {
    name: "zf",
    age: 19
}

function lzf(x, y) {
    console.log(x + "," + y);
    console.log(this);
    console.log(this.name);

}

lzf.bind(Person, 4, 5); //只是更改了this指向,没有输出
lzf.bind(Person, 4, 5)(); //this指向Person-- 4,5  Person{}对象  zf
  • 4.存储this指向到变量中
var oDiv1 = document.getElementById("div1");
oDiv1.onclick = function () {
    var _this = this; //将this储存在变量中,而且不改变定时器的指向
    setTimeout(function () {
        console.log(_this); //注意这里是_this,而不是this-- <div id="div1">点击</div>
        console.log(this); //定时器的指向没有被改变--仍然是window
    }, 1000)
}

10. 手动实现call() , apply() , bind()

js实现call()-----------------------------------------------
Function.prototype.call = function (context, ...args) {
  // 因为传进来的 context 有可能是 null
  context = context || window;
  // 将函数设为对象的属性 =》 让 fn 的上下文为 context
  context.fn = this;
  const result = context.fn(...args);
  delete context.fn;
  return result;
}
function text(a, b, c) {
  console.log(this, a, b, c); //{ test: 22, fn: [Function: text] } 1 2 3
}
text.call({ test: 22 }, 1, 2, 3)

js实现bind()-----------------------------------------------

11. 箭头函数


  • 1.箭头函数没有function关键字
  • 2.小括号和大括号之间有箭头
  • 3.如果参数是一个,可以身略小括号
  • 4.如果没有return,可以不用写大括号
  • 5.如果返回的是对象类型,需要用小括号包裹一下
  • 6.函数可以赋予默认参数
function fn(a){
    return function fn1(b){
        return a+b
    }
}

<!--等价于下面的箭头函数-->
let fn = a => b => (a+b)

fn(1)(2)
  • 7.解决this问题
    • var that = this
    • 通过bind绑定this(apply,call)
  • 8.箭头函数里面的this是在定义它时绑定的,指向它的父级作用域这个this的指向是不能通过call和apply改变的。),也没有arguments.
<!--对象不是作用域,所以这里箭头函数的父级是window-->
let obj = {
  a: () => {
    console.log(this)
  }
}
obj.a() //window


<!--函数有自己的作用域,箭头函数的父级作用域就是构造函数obj-->
function obj() {
  this.a = () => {
    console.log(this)
  }
}

let obj1 = new obj()
obj1.a()  //obj

<!--没有arguments-->
// ...叫剩余运算符,把多余的都放到数组中
let fn = (x, ...args) => {
  console.log(args) //[1,2,3,4]
}
fn('x', 1, 2, 3, 4)

12. 构造函数new之后发生了什么,怎么用函数模拟new的行为


function Person(name,age){
  this.name = name;
  this.age = age
}

// let student = new Person('zf',18)
// console.log(student)

<!--方法一:-->
function copyNew(obj,...args){
  // 1.创建了一个空对象
  const newObj={}
  // 2.空对象的原型指向了构造函数的原型
  newObj.__proto__=obj.prototype
  // let newObj=Object.create(obj.prototype)   //1.2可以合并为一步,创建以obj.prototype为原型的对象
  // 3.让this指向新创建的空对象
  let result=obj.apply(newObj,args)
  // 4.判断返回值的类型,如果是值类型就返回新创建的对象,如果是引用类型,就返回这个引用类型的对象
	return typeof result ==='object'?result:newObj
}

<!--方法二:-->
function copyNew(...args){
    let con = [].shift.call(args)
    let obj = Object.create(con.prototype)
    con.apply(obj,args)
    return typeof result ==='object'?result:newObj
}

console.log(copyNew(Person,'zf',18))

13 类class

  • 类只能通过new
  • 类可以继承公有私有和静态方法
  • 类的构造函数中,返回一个引用类型,会把这个引用类型作为子类的this
class Parent{
    constructor(){//每个实例私有的属性
        this.name="parent"
    }
    static b(){  //类上的方法
        return 2
    }
    eat(){      // 实例原型上的方法
        console.log('eat')
    }
}
class Child extends Parent{ //要求继承父亲的私有和公有
    constructor(){
        super()  //相当于Parent.call(this)
        this.age = 9  //私有属性
    }
    static a(){ //类上的方法
        return 1
    }
    smoking(){  //原型上的方法
        console.log('smoking')
    }
}
let child = new Child()

14. 如何解决多并发问题

  • 计数器
  • 发布订阅模式,发布和订阅没有关系
  • 观察者模式,观察和被观察者有关系
  • promise

15.介绍一下你对浏览器内核的理解

  • 主要分成两个部分:渲染引擎(Render Engine)和JS引擎
    • 渲染引擎:负责取得网页的内容(html,xml和图像等),整理讯息(例如假如css),以及计算网页的显示方式,然后输出到显示器或打印机
    • JS引擎:解析和执行JavaScript来实现网页的动态效果。

16. 常见的浏览器内核有哪些?

  • webkit:Safari,Chrome
  • Trident:IE,360
  • gecko:firefox
  • opero:blink

17.cookies,sessionStorage 和 localStorage 的区别?

  • 储存大小:
    • cookie:数据大小不能超过4k
    • sessionStorage和localStorage虽然也有存储大小的限制,但比cookie大得多,可以达到5M或更大
  • 有期时间:
    • localStorage 永久储存,浏览器关闭后数据不丢失除非主动删除数据
    • sessionStorage 数据在当前浏览器窗口关闭后自动删除
    • cookie设置的过期时间之前一直有效,即使窗口或浏览器关闭

18. 防抖:即短时间内大量触发同一个事件,函数只执行一次。

  • 实现原理为:设值一个定时器,约定在**毫秒后再触发事件处理,每次触发事件处理都要重置计时器。
  • 通常用于搜索框、滚动条的监听事件处理。
function debounce(func,wait){
    let timeout = null;
    return function(){
        let context = this;
        let args = arguments;
        if(timeout) clearTimeout(timeout);
        timeout = setTimeout(()=>{
            func.apply(context,args)
        },wait)
    }
}

19. 防抖是延迟执行,节流是间隔执行,即每隔一段事件执行一次

  • 实现原理为:设置一个定时器,约定**毫秒后执行,如果时间到了,则执行函数并重置定时器。
  • 和防抖的区别,防抖是每触发一次事件就重置定时器,节流是到时间后重置定时器。
function throttle(){
    let timeout = null;
    return function(func,wait){
        let context = this;
        let args = arguments;
        if(!timeout){
            timeout = setTimeout(()=>{
                timeout = null;
                func.apply(context,args)
            },wait)
        }
    }
}
  • 方法二:时间戳
  • 实现原理:使用两个时间戳prev旧时间戳和now新时间戳,每次触发事件都判断二者的时间差,如果到达规定时间,执行函数并重置旧时间戳
function throttle(func,wait){
    var pre = 0;
    return function(){
        let now = new Date();
        let context = this;
        let args = arguments;
        if(now-pre>wait){
            func.apply(context,args)
            pre = now
        }
    }
}

20. 如何配置es6运行环境

1、下载和安装Visual Studio Code
2、下载和安装Node.js
3、安装全局的babel
npm install babel-cli babel-eslint -g
4、使用npm init -y命令创建工程
"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node index.js" //自己配置,运行npm run start即可以运行index.js文件
 },
5、本地安装babel-preset-es2015 和 babel-cli(编译插件)
npm install --save-dev babel-preset-es2015 babel-cli 
(package.json文件会自动多了devDependencies选项。其实还创建了package-lock.json文件和node_modules文件夹)
6、新建.babelrc(ES6转化为ES5的语法)
创建.babelrc文件,粘贴以下代码
 { "presets":[ "es2015" ], "plugins":[] }