前端面试知识点大全——JS篇(四)

总纲:前端面试知识点大全

目录

1.javascript调试工具

2.对象遍历 和 数组遍历

3.可变对象和不可变对象

4.什么是事件循环 (event loop)

5.let var const

6.数组的方法

7.web worker

7.1 使用Web Worker

7.2 关闭worker

7.3 web worker可以访问的特性

7.4 同域限制

7.5 subWorker

7.6 worker的用处

7.7 web worker的分类

7.8 线程之间postMessage通信

8.柯里化

8.1 函数柯里化的概念

8.2 思想思路

8.3 通用函数柯里化方法

8.4 实现add(1)(2)(3)

8.5 函数柯里化的作用

8.6 简单利用柯里化实现bind函数

 9.创建对象的三种方法

9.1 工厂模式

9.2 构造函数模式

9.3 原型模式

9.4 组合使用构造函数模式和原型模式

9.5 动态原型模式

9.6 寄生构造函数模式

9.7 稳妥构造函数模式

10.深拷贝和浅拷贝

10.1 浅拷贝

10.2 深拷贝


1.javascript调试工具

浏览器自带的调试工具,比如firebug

语法检查工具ESlint

Mocha测试框架,只需在Mocha提供的全局函数,比如describe,it就是最核心的两个全局函数,可以在测试中直接使用。

decribe函数被用来包裹一组相关的测试。它是一个函数,第一个参数是这组测试的描述,第二个参数是一个实际执行的函数。

it函数则用于包裹一个单独的测试。它也是一个函数,第一个参数是名称,第二个参数是一个实际执行的函数。

2.对象遍历 和 数组遍历

对象遍历:for...in...,Object.keys(obj)

数组遍历:for..in..,for..of..,forEach,map,filter,every,some

3.可变对象和不可变对象

可变对象:引用类型

不可变对象:基本类型

防篡改对象:不可拓展对象 Object.preventExtension(obj) Object.isExtensible();密封对象 Object.seal(obj),不能拓展,且已存在的属性删除,也不能添加,可以修改;冻结对象Object.freeze(obj),不能拓展、删除添加修改Object.isFrozen(obj)

4.什么是事件循环 (event loop)

setTimeout运行的时间间隔约是4ms(另外,setInterval也是一样的),实事上,这正是浏览器两次Event Loop之间的时间间隔,相关标准各位可以自行查阅。另外,在Node中,这个时间间隔跟浏览器不一样,是1ms。

主线程和任务队列,主线程空闲,就会循环调用任务队列。主线程空闲,取出任务队列的第一项放入(主线程的)执行栈,重复上述步骤。其中Promise和process.nextTick是微任务,加入当前任务的尾部,而setTimeout和setInterval是放到下一个任务队列的首部。

 

1、一个线程中,事件循环是唯一的,但是任务队列可以拥有多个。

2、任务队列又分为macro-task(宏任务)与micro-task(微任务),在最新标准中,它们被分别称为task与jobs。

3、macro-task大概包括:script(整体代码), setTimeout, setInterval, setImmediate, I/O, UI rendering。

4、micro-task大概包括: process.nextTick, Promise, Object.observe(已废弃), MutationObserver(html5新特性)

5、setTimeout/Promise等我们称之为任务源。而进入任务队列的是他们指定的具体执行任务。

6、来自不同任务源的任务会进入到不同的任务队列。其中setTimeout与setInterval是同源的。

7、事件循环的顺序,决定了JavaScript代码的执行顺序。它从script(整体代码)开始第一次循环。之后全局上下文进入函数调用栈。直到调用栈清空(只剩全局),然后执行所有的micro-task。当所有可执行的micro-task执行完毕之后。循环再次从macro-task开始,找到其中一个任务队列执行完毕,然后再执行所有的micro-task,这样一直循环下去。

8、其中每一个任务的执行,无论是macro-task还是micro-task,都是借助函数调用栈来完成。

 

5.let var const

let和const是ES6提出来的声明变量的标识符,他们定义的变量具有块级作用域。var声明的变量没有块级作用域。

let和const声明的变量不会提升,而var声明的变量会提升。

let和var声明的变量是可变的,const声明的变量是不可变的。

let和const存在暂时性死区。块级作用域内,在let声明变量之前,使用变量会导致暂时性死区。ES6 明确规定,如果区块中存在let和const命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。

let和const都不允许在相同作用域内,重复声明同一个变量。var可以重复声明定义同一个变量,后面的会覆盖前面定义的。

6.数组的方法

1、push、unshift返回添加了新元素后的数组长度

2、pop和shift返回被删除的元素

3、concat可以给数组添加多个元素并返回新数组的引用。若给concat传一些数组作为参数,它会把这些数组拆分后的元素添加到原始数组的拷贝中,原数组不会变

4、slice(start,end)返回新数组的引用,原数组不变

5、splice(start,length,[params]),会改变原数组,返回删除的子数组(若无,则为[])

6、reverse,颠倒数组的排序

7、sort,指定排序函数,返回'true'则改变顺序,返回'false'则不改变顺序。默认情况是按字符串的ASCII码进行升序排列

8、map,返回新数组,数组中的元素为原始数组元素调用函数处理后的值。

9、filter,创建一个新的数组,新数组中的元素是根据回调函数返回true的元素。

10、reduce(),接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。回调函数的第一个参数为累加值,其余跟别的回调函数一样,元素、索引、数组本身。

11、copyWithin(target,start,end),将数组中一串有序的元素复制到数组的另一个位置(target),复制的同时会覆盖原来数组中的内容。参数可以为负数。(不含end位)

12、fill(value,start,end),可以将一个固定值赋值给数组中的任意位置元素,且会修改当前数组,同样不包含end位

13、some,支持回调函数,原数组中至少一个满足回调函数返回true,则返回true

14、every,支持回调函数,原数组中的每一项都让回调函数返回true,则返回true

15、indexOf,返回字符串的在数组中的位置,可以设置起始位置

16、findIndex,传入回调函数,返回符合回调函数的元素的下标

17、find,传入回调函数,返回符合回调函数的元素

18、forEach,数组中的每一项执行回调函数

19、of,用于直接生成数组,可以传入数组元素。为了解决Array对象实例化是,传入单个参数时带来的误差。new Array(3)有三个未定义的元素,Array.of(3)只有一个3的数组

20、from,将类数组或者具有iterator接口的对象转成数组

7.web worker

这是H5提出的新API,它由浏览器提供,可以启动一个独立于网页的线程,另辟蹊径的解决JS单线程的问题,但是并未改变JS是单线程的现实。通过postMessage(msg)进行线程间的双向通信,通过message时间监听数据传输。

7.1 使用Web Worker

//main.js传递一个Object
let worker  = new Worker('worker.js');
worker.addEventListener('message', function (e) {
    console.log(e.data.a);
});
worker.addEventListener('error', function (e) {
    console.log(’error‘);
});
let msg = {a:2};
worker.postMessage(msg);

//worker.js 接收,并返回
self.addEventListener('message', function (e) {
    e.data.a = 3;
    self.postMessage(e.data);
});

在worker中,self就是new Worker的实例化内容。

通过postMessage传递的msg并不是两个线程共享的,实际上只是一个副本。

7.2 关闭worker

(1)worker.terminate(),在外部终结worker

(2)self.close(),在worker内部终结

官方推荐使用self.close()进行内部的自动关闭,这样能防止意外关闭正在运行的worker

7.3 web worker可以访问的特性

worker的线程与JS主线程存在一定区别,它无法访问和操作dom

(1)window.navigator对象的相关属性和方法

(2)只读window.location内容

(3)可使用XMLHttpRequest与后台通信

(4)setInterval、setTimeout相关函数

除以上几个特性外,worker不能访问别的特性或者API

7.4 同域限制

worker在访问的JS文件只能是在同一host下,即worker只能处于指定目录下的path中。

若本地调试,直接打开页面(file://XXX)这种,也不能使用worker。

7.5 subWorker

在一个worker中可以在调用其他的worker,当然也有同域限制。

如果想在当前worker里面加载其他库文件,需要使用importScripts方法导入,如importScripts('react.js','jquery.js')。

7.6 worker的用处

(1)懒加载数据

(2)文本分析

(3)流媒体数据处理

(4)web database的更新

(5)大量JSON返回数据的处理

7.7 web worker的分类

(1)Dedicated worker(DW,专用worker):即上面介绍的,使用new Worker创建,该worker一般只能在创建worker的js脚本中使用。

(2)shared workers(SW):使用new shareWorker()创建,能在不同JS脚本中使用。

SW的特性:1、引入外部文件,importScripts();2、错误监听(error事件);3、关闭通信,port.close();4、可以使用XMLHttpRequest对象;5、navigator、location、setInterval的访问

7.8 线程之间postMessage通信

//第一种传递方式
worker.postMessage(message,taransferList(是个数组));
//第二种传递方式
worker.postMessage({ 
     operation: "list_all_users", 
     //ArrayBuffer object 
     input: buffer, 
     threshold: 0.8, 
}, [buffer]);

第二个参数是用于传递大对象(例如ArrayBuffer),直接从一个上下文移交到另外一个上下文。如果主线程JS到Web Worker线程。一旦提交成功,这个大对象在原有的上下文中,将不可访问。

8.柯里化

8.1 函数柯里化的概念

把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。

8.2 思想思路

1、闭包保存args变量,存储之前的参数

2、新建一个_add函数,参数的长度为0,就执行加法,否则,存储参数到args,然后返回函数自身(可以选择匿名函数,返回arguments.callee即可,但是在严格模式下不能使用,所以还是使用方法一比较稳妥)。

function add() {
    //建立args,利用闭包特性,不断保存arguments
    let args = [].slice.call(arguments);
    //方法一,新建_add函数实现柯里化
    let _add = function () {
        if (arguments.length === 0) {
            //参数为空,对args执行加法
            return args.reduce(function (a, b) {
                return a + b
            });
        } else {
            //否则,保存参数到args,返回一个函数
            [].push.apply(args, arguments);
            return _add;
        }
    };
    //返回_add函数
    return _add;
}

8.3 通用函数柯里化方法

//  通用的函数柯里化构造方法
function curry(func){
    //新建args保存参数,注意,第一个参数应该是要柯里化的函数,所以args里面去掉第一个
    let args = [].slice.call(arguments,1);
    //新建_func函数作为返回值
    let _func =  function(){
        //参数长度为0,执行func函数,完成该函数的功能
        if(arguments.length === 0){
            return func.apply(this,args);
        }else {
            //否则,存储参数到闭包中,返回本函数
            [].push.apply(args,arguments);
            return _func;
        }
    }
    return _func;
}

8.4 实现add(1)(2)(3)

思路:返回一个函数

//可拓展
function add(x) {
     var sum = x;
     var tmp = function (y) {
         sum = sum + y;
         return tmp;
     };

     tmp.toString = function () {
         return sum;
     };
     return tmp;
}

//不可拓展
function add(x) {
    return function (y) {
        return function(z) {
            return x+y+z;
        }
    }
}

8.5 函数柯里化的作用

1、延迟计算。

2、参数复用。当在多次调用同一个函数,并且传递的参数绝大多数是相同的,那么该函数可能是一个很好的柯里化候选。

3、动态创建函数。这可以是在部分计算出结果后,在此基础上动态生成新的函数处理后面的业务,这样省略了重复计算。或者可以通过将要传入调用函数的参数子集,部分应用到函数中,从而动态创造出一个新函数,这个新函数保存了重复传入的参数(以后不必每次都传)。例如,事件浏览器添加事件的辅助方法

8.6 简单利用柯里化实现bind函数

bind函数本身也是一个柯里化函数

Function.prototype.bind = function(){
    var fn = this;
    var args = Array.prototye.slice.call(arguments);
    var context = args.shift();
    return function(){
        return fn.apply(context,
            args.concat(Array.prototype.slice.call(arguments)));
    };
};

 9.创建对象的三种方法

一个对象实例的创建方式

方法1:字面量方法

方法2:利用Object构造函数,new Object();

方法3:Object.create(null) 创建的对象是一个空对象,在该对象上没有继承 Object.prototype 原型链上的属性或者方法,例如:toString(), hasOwnProperty()等方法。

Object.create()方法接受两个参数:Object.create(obj,propertiesObject) ;

 

别的意义上的创建

9.1 工厂模式

每次都是返回一个包含2个属性和一个方法的对象(工厂的产品的一致性)。虽然解决了创建多个相似对象的问题,但却没有解决对象识别的问题(即怎样知道一个对象的类型)

function createPerson(name,age) {
    let obj = new Object();
    obj.name = name;
    obj.age = age;
    obj.sayName = function () {
        console.log(this.name);
    };
    return obj;
}

9.2 构造函数模式

解决了工厂模式的问题,但是每个方法都要在每个实例上重新创建一遍。

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

9.3 原型模式

原型模式解决了构造函数中方法重复创建的问题,但是实例属性也变成了共享,一旦某个实例改变了属性,那么其他实例的属性也跟着改变。同时他也没有办法在实例化的时候初始化参数。

function Person(name,age) {
}
Person.prototype.name = 'David';
Person.prototype.age = 25;
Person.prototype.sayName = function () {
    console.log(this.name);
};

9.4 组合使用构造函数模式和原型模式

兼容两种模式的优点,目前主流的方式

function Person(name,age) {
    this.name = name;
    this.age = age;
}
Person.prototype.sayName = function () {
    console.log(this.name);
};

9.5 动态原型模式

只有原型上不存在某个方法或者属性的时候才会添加

function Person(name, job) {
    this.name = name;
    this.age = age;
    if ( typeof this,sayName != 'function') {
        Person.prototype.sayName = function(){
            console.log(this.name);
        }
    }
}

9.6 寄生构造函数模式

和工厂模式一模一样,但是是通过new操作进行实例化。即new Person

但需要注意的是:返回的对象与构造函数或者与构造函数的原型属性之间没有关系,即构造函数返回的对象与在构造函数外部创建的对象没有什么区别。

instanceof来确定对象类型没用,都是object的实例。

function Person(name,age) {
    let obj = new Object();
    obj.name = name;
    obj.age = age;
    obj.sayName = function () {
        console.log(this.name);
    };
    return obj;
}

9.7 稳妥构造函数模式

稳妥构造函数遵循的寄生构造函数类似的模式,但稳妥构造函数一是新创建的实例方法不引用this;二是不使用new操作符调用构造函数

function Person(name, age) {
    //创建要返回的对象
    let o = new Object();

    //这里可以定义私有变量和函数

    //添加方法
    o.sayName = function() {
        console.log(name);
    };
    return o;
}

10.深拷贝和浅拷贝

浅拷贝与深拷贝都可以实现在已有对象上再生出一份的作用。但是对象的实例是存储在堆内存中然后通过一个引用值去操作对象,由此拷贝的时候就存在两种情况了:拷贝引用和拷贝实例,这也是浅拷贝和深拷贝的区别。

JS中基本类型的复制操作是深拷贝,引用类型的复制是浅拷贝。

10.1 浅拷贝

function simpleClone(initalObj) {    
      var obj = {};    
      for ( var i in initalObj) {
        obj[i] = initalObj[i];
      }    
      return obj;
}

Object.assign(target, source),这是ES6的新API。用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。source可以是多个对象,以逗号分隔。

如果目标对象中的属性具有相同的键,则属性将被源中的属性覆盖。后来的源的属性将类似地覆盖早先的属性。

10.2 深拷贝

a) 利用JSON.parse(JSON.stringify(obj))

可以实现对象的深拷贝,但是具有以下缺点:

I、undefined、任意的函数、正则表达式类型以及 symbol 值,在序列化过程中会被忽略(出现在非数组对象的属性值中时)或者被转换成 null(出现在数组中时);

II、它会抛弃对象的constructor。也就是深拷贝之后,不管这个对象原来的构造函数是什么,在深拷贝之后都会变成Object;即继承属性会丢失

III、如果对象中存在循环引用的情况无法正确处理。

b) 利用递归实现深拷贝

function deepCopy1(obj) {
  // 创建一个新对象
  let result = {}
  let keys = Object.keys(obj),
    key = null,
    temp = null;
 
  for (let i = 0; i < keys.length; i++) {
    key = keys[i];  
    temp = obj[key];
    // 如果字段的值也是一个对象则递归操作
    if (temp && typeof temp === 'object') {
      result[key] = deepCopy1(temp);
    } else {
    // 否则直接赋值给新对象
      result[key] = temp;
    }
  }
  return result;
}

上面这个方法没有考虑循环引用和同级引用,改良上面的方法

function deepCopy3(obj) {
  // hash表,记录所有的对象的引用关系
  let map = new WeakMap();
  function dp(obj) {
    let result = null;
    let keys = Object.keys(obj);
    let key = null,
      temp = null,
      existobj = null;
 
    existobj = map.get(obj);
    //如果这个对象已经被记录则直接返回
    if(existobj) {
      return existobj;
    }
 
    result = {}
    map.set(obj, result);
 
    for(let i =0,len=keys.length;i<len;i++) {
      key = keys[i];
      temp = obj[key];
      if(temp && typeof temp === 'object') {
        result[key] = dp(temp);
      }else {
        result[key] = temp;
      }
    }
    return result;
  }
  return dp(obj);
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值