【JS面试大全上】

468 阅读12分钟

前言

总结超多常见的JS基础知识点,将网络上大多数文章都看了一遍。
再规划一遍,有什么问题都没有答案,自己大部分从掘金查找优质的文章。
因为都是零碎的问题,找的问题也太多或大致相同。
也就顾及不到什么版权问题,望见谅!嘻嘻
为自己大三下实习打好基础!自己要加油!!
也整理给有需要的小朋友们 ✨

JS基础

1. js 的基本数据类型(值类型)。

ES6及之前(5种)

String、Number、Boolean、Null、Undefined

ES6之后ES10之前(6种)

String、Number、Boolean、Null、Undefined、Symbol

ES10之后(7种)

String、Number、Boolean、Null、Undefined、Symbol、BigInt

2. js 的复杂数据类型(引用类型)。

对象(Object)、数组(Array)、函数(Function)

3. js有几种类型的值

栈:基本数据类型

堆:引用数据类型

两者区别:

1. 声明变量时不同内存位置不同:

​ 基本数据类型变量的值储存在栈中,这是因为基本数据类型占据的空间内存是固定的,因此可以直接访问。

​ 引用数据类型储存在栈中的是变量的地址指针,指向堆中的引用。因为引用数据类型的大小会改变因此不能储存在栈中会影响查找的速率。

2.访问机制不同:

​ 在js中是不允许直接访问储存在堆内存中的变量的,能够访问的是堆内存中的地址,然后按照地址去访问引用类型

​ 栈中的数据变量是可以直接访问到的。

3.复制变量时不同:

​ 栈中的变量复制时会将数值保存为另一个副本,这两者是相互独立的。

​ 而堆中的对象进行复制时是将保存着内存地址的变量赋值给另一个变量,两者引用地址相同,它们中任何一个进行修改时都会改变另一个变量。

4.传递参数时不同:

​ 传递参数是将变量的值传递给形参,引用数据类型中的值为该变量在堆内存中的地址,

​ 因此将引用数据类型作为参数传递时在函数内部对变量进行修改会影响函数外部该引用数据的值。

4.什么是堆?什么是栈?它们之间有什么区别和联系?

概念

栈和堆的概念来自于从数据结构和操作系统中。

数据结构中栈为先进后出队列,而堆为优先级队列,二叉树为典型的堆队列。

区别和联系

申请方式

  1. 堆:由程序员自己申请并指明大小的
  2. 栈:系统自动分配

申请大小的限制

  1. 堆:由低地址到高地址扩展的数据结构,是不连续的内存区域, 这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。

  2. 栈:在windows下,栈是由高向低地址扩展的数据结构, 是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在WINDOWS下,栈的大小是2M(也有的说是1M,总之是 一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。

申请效率

  1. 堆:由类似new机制来分配内存,一般速度比较慢,而且容易产生内存碎片,这一点从分配机制上能解释,不过用起来比较方便。

  2. 栈:由系统自动分配,速度比较快,而且程序员是无法控制的。

时效性

  1. 堆:持久化
  2. 栈:临时

上下文调用

  1. 堆:全局

  2. 栈:局部

内存

  1. 栈区内存 - 由编译器自动释放,主要储存函数的参数、局部变量等,结束后自动销毁。

  2. 堆区内存 - 主要靠程序员手动释放,若没有手动释放,垃圾回收机制会将其回收。

5.内部属性 [[Class]] 是什么

所有用typeof返回为object的变量都含有一个内部属性[[Class]],可以看成是内部的分类

利用Object.prototype.toString.call()可返回该分类。

例如Object.prototype.toString.call([]) 返回**[[Object Array]]**

6.介绍 js 有哪些内置对象

js中的内置对象指的是在操作前由js定义的存在于全局作用域中的全局值属性、函数对象、以及可实例化的构造函数。

全局值属性例如NaN、null等,函数有parseInt、parseFloat等

可实例化其他对象的构造函数Number、Boolean、Function等以及还有Date、数学对象Math等。

7.undefined 与 undeclared 的区别

  1. 已在全局中声明但还没有赋值的变量返回undefined
  2. 未声明的变量是undeclared。会报错返回is not defined。

8.null 和 undefined 的区别

首先null和undefined都是基本数据类型。

  1. undefined表示变量未定义,null代表的含义是空对象

  2. 一般变量未定义的时候会返回undefined,null一般作为变量对象的初始值(类似于占位符)

  3. typeof null 会返回object。

  4. null == undefined 返回true,但三个等号返回false。

9.如何获取安全的 undefined 值

原因:undefined 是一个标识符,所以可以被当作变量来使用和赋值,但是这样会影响 undefined 的正常判断。

想法:表达式 void ___ 没有返回值,因此返回结果是 undefined。

结果:void 并不改变表达式的结果,只是让表达式不返回值。

使用 void 0 来获得 undefined。

![image-20200928155305342](/Users/lin/Library/Application Support/typora-user-images/image-20200928155305342.png)

10.说几条写 JavaScript 的基本规范

遵循一些基本规范利于读者阅读以及日后维护

  1. 变量声明尽量放在作用域的前面,并且var声明时最好给予初始值。
  2. 用 '===' 和 '!==' 来代替 '==' 和 '!='。
  3. 不要给内置对象的原型对象上添加方法,例如Array,Object,Function等。
  4. 代码中出现地址、时间等常量用变量来代替。
  5. switch语句必须带有default分支。
  6. if和for语句要有大括号。

11.JavaScript原型,原型链? 有什么特点

在JavaScript中我们使用构造函数创建一个实例对象时

每个构造函数内部都有一个prototype属性,这个属性是一个对象 => 原型对象

实例对象内部的 proto 属性指向构造函数的原型对象并且该原型对象也可看成其他构造函数的实例,这个proto属性链就是原型链

当我们要查找实例对象身上的某个属性及方法时,若该实例对象身上没有,可沿着proto属性一级一级向上找,直至Object.prototype

特点:JavaScript中是利用引用来进行传递的,当我们修改了某一原型的属性时,所有继承都会被修改。

12. JavaScript 获取原型的方法

function R(){} let one=new R();

  1. console.log(Object.getPrototypeOf(one)); // 官方推荐
  2. console.log(one.proto);
  3. console.log(one.constructor.proto);

13. 在 js 中不同进制数字的表示方式

  1. 0X、0x开头的为16进制
  2. 0O、0o开头的为8进制
  3. 0B、0b开头的为2进制

14. js 中整数的安全范围是多少

安全整数指的是该整数转换为二进制时精度不会丢失

最大值指的是2的53次幂-1,超过安全整数范围在计算时会有误差。

在ES6中被定义为Number.MAX_SAFE_INTEGERNumber.MIN_SAFE_INTEGER。当超过整数范围时会返回infinity

15.typeof NaN 的结果是什么

  1. typeof NaN会返回 number
  2. NaN是一个特殊值,它与自身不相等,NaN !=NaN => true

16.isNaN 和 Number.isNaN 函数的区别

isNaN接收参数时会尝试将其转化为数值型再判断

因此传入的不能转换为数值的会返回true,但非数值型也会返回true,影响了NaN的判断。

Number.isNaN会先判断其是否为Number,然后在进行isNaN判断。判断更为准确。

17.Array 构造函数只有一个参数值时的表现

Array构造函数只有一个参数值时会让其视为创建数组的长度length值,而非充当一个元素。

但创建出来的数组依然是个空数组但有预设长度值

18.其他值到字符串的转换规则

规范的 9.8 节中定义了抽象操作 toString() ,它负责处理非字符串到字符串的强制类型转换。

  1. Null 和 Undefined 类型 ,null 转换为 "null",undefined 转换为 "undefined",
  2. Boolean 类型,true 转换为 "true",false 转换为 "false"。
  3. Number 类型的值直接转换,不过那些极小和极大的数字会使用指数形式
  4. Symbol 类型的值直接转换,但是只允许显式强制类型转换,使用隐式强制类型转换会产生错误。
  5. 对普通对象来说,除非自行定义 toString() 方法,否则会调用 toString()(Object.prototype.toString()) 来返回内部属性 [[Class]] 的值,如"[object Object]"。如果对象有自己的 toString() 方法,字符串化时就会 调用该方法并使用其返回值。

19.其他值到数字值的转换规则

  1. undefined返回NaN。

  2. null返回0.

  3. true返回1,false返回0.

  4. 字符串类型的值转换为数值型如同利用Number(),若字符串中含有非数字型返回NaN空字符串返回0.

20.其他值到布尔类型的值的转换规则

转换为false的有六种:

null、undefined、false、""、NaN、+0、-0

其余为true

21.{} 和 [] 的 valueOf 和 toString 的结果是什么

  1. {}的valueOf为**{},toString为[Object Object]**

  2. []的valueOf为**[], toString为""**

22.什么是假值对象?

在某些情况下,浏览器在一些常规的js基础上自行创建的一些对象

这些对象强制转换为布尔值时为false

例如document.all为一个伪数组,表示页面中所有元素的数组,由DOM提供给js使用。

23. ~ 操作符的作用?

  1. ~表示按位取反
  2. ~~可以用于取整

24.解析字符串中的数字和将字符串强制类型转换为数字的返回结果都是数字,它们之间的区别是什么?

  1. 解析字符串中的数字允许含有非数字,例如parseInt解析字符串时会返回开头的数值,若第一个字符为非数字,则返回NaN
  2. 类型转换Number()解析字符串时字符中不能含有不合法字符。否则返回NaN.

25. + 操作符什么时候用于字符串的拼接?

  1. 当+操作符前后两个变量至少一个为字符串时,两者用+连接为字符串拼接。
  2. 若两者都为数字,则会数字加法运算。
  3. 而除了+以外的其他运算符,只要其中一方为数字,另一方就会转换为数字。

26.什么情况下会发生布尔值的隐式强制类型转换?

条件判断语句的时候

例如在if语句中进行判断时会转换为布尔值,还有while语句。三项表达式。for( ; ; )中的第二项。逻辑运算符||和&&进行判断时。

27. || 和 && 操作符的返回值?

  1. 首先会对第一项进行布尔值强制类型转换。
  2. 运用||运算符时,当第一项为true,则直接返回true,当第一项为false,则返回第二项的布尔值
  3. 运动&&运算符时,当第一项为true,则返回第二项的布尔值,当第一项为false,则直接返回false。

28. Symbol 值的强制类型转换?

  1. symbol值可以进行显性类型转换 但不能进行隐形类型转换会报错。
  2. symbol值不能转换为数值型,但可以转化为布尔值,不管是显性还是隐性都是true

29.== 操作符的强制类型转换规则?

  1. 字符串和数值型进行==比较时,将字符串转换为数值型再进行比较。
  2. 其他类型跟布尔值进行比较时,先将布尔值转换为数值型,再进行其他比较。
  3. NaN和本身取==时为false
  4. null == undefined 为true
  5. 如果两个操作值都是对象,则需比较两者是否为同一个引用对象。

30.如何将字符串转化为数字,例如 '12.3b'?

  1. 使用Number()方法,但前提是所包含的字符串不包含不合法字符。
  2. parseInt()方法,取整。
  3. parseFloat()方法,浮点数
  4. 隐式类型转换

31.如何将浮点数点左边的数每三位添加一个逗号,如 12000000.11 转化为『12,000,000.11』?

使用正则表达式方法

function format(number){  return number && number.replace(/(\d)(?=(\d{3}+\.))/g, function($1, $2, $3){  return $2 + ',';  }) }

?=pattern 表示匹配到pattern的开始位置的字符,例如window(?=95|98|2000|xp),可以匹配到window2000中的window。

32. 生成随机数的各种方法?

Math.random()

33. 如何实现数组的随机排序

  1. 随机抽取数组元素到新数组中:

    function randomsort(arr){  
    	var newarr = [];  
      while(arr.length > 0){   
        var index = Math.floor(Math.random()*arr.length);   
        newarr.push(arr[index]);   
        arr.splice(index, 1);  
      }  
      return newarr; 
    }
    
  2. 随机交换数组内的元素(洗牌法)

function randomsort(arr){  
  var temp,  leng = arr.length,  tempindex;  
  for(var i = 0; i < leng; i++){  
    tempindex = Math.floor(Math.random()*(leng - i) + i);  
    temp = arr[i];  
    arr[i] = arr[tempindex];  
    arr[tempindex] = temp;
  }  
  return arr; 
}

34. javascript 创建对象的几种方式?

// 1.最直白的方式:字面量模式创建
let obj = {
  name: '大大',
  age: 20,
  sex: '男'
}

// 2.调用系统构造函数创建
let obj = new Object()
obj.name = '大大'
obj.age: 20,
obj.sex: '男'

// 3.工厂模式创建
function createPerson(name, age, sex){
  let p = new Object();
  p.name = name;
  p.age = age;
  p.sex = sex;
}
var person1 = createPerson("大大1", 20, '男');
var person2 = createPerson("大大2", 18, '男');

// 4.自定义构造函数创建
function Person(name, age, sex){
  this.name = name;
  this.age = age;
  this.sex = sex;
}
var person1 = new Person("大大1", 20, '男');
var person2 = new Person("大大2", 18, '男');
/** 这种方法与工厂模式创建有几个不同点:
1.函数名Person是大写,而工厂模式的函数creatPerson是小写(俗成约定);
2.该方法没有return语句,而工厂函数创建有return;
3.该方法直接将属性和方法赋值给了this对象,而工厂模式没有出现this对象;
4.该方法通过new操作符来实例化一个对象,而工厂模式实例化对象直接调用函数即可。**/

// 5.原型模式创建对象
function Person(name, age, sex){
  this.name = name;
  this.age = age;
  this.sex = sex;
}
var person1 = new Person("大大1", 20, '男');
var person2 = new Person("大大2", 18, '男');

35. js 继承的几种实现方式?

  1. 原型链继承:将子构造函数的原型对象指向父构造函数的实例对象,那么子构造函数的实例对象可继承父类上的属性及方法。缺点是创建子类时不能向父类传参,并且父类原型上的所有引用类型可应用到所有实例对象上。

    function Father(name, age){  
      this.name = name;  
      this.age = age; 
    } 
    Father.prototype.getName = function(){  
      return this.name;
    } 
    function Child(skill){  
      this.skill = skill; 
    } 
    Child.prototype = new Father('zhangsan', 20); 
    var child = new Child('dance'); 
    console.log(child.getName());
    
  2. 构造函数继承:通过在子类中使用对父构造函数使用call方法来调用,并且修改this指针指向子类,同时可以传递参数。优点:避免了引用类型的属性被所有实例共享,也解决了不能传参的问题。缺点是因为方法都在构造函数中定义了,因此每次创建实例时都要创建一遍方法。

    function Father(name, age){  
      this.name = name;  
      this.age = age;  
      this.getName = function(){   
        return this.name;
      }  
      this.getAge = function(){   
        return this.age;
      } 
    } 
    function Child(name, age, skill){  
      Father.call(this, name, age);  
      this.skill = skill 
    } 
    var child = new Child('zhangsan', 20, 'dance'); 
    console.log(child.getName())
    
  3. 组合继承:通过结合了原型链继承和构造函数继承。

    function Father(name, age){  
      this.name = name;  
      this.age = age; 
    } 
    Father.prototype.money = function(){  
      console.log('100000'); 
    } 
    function Child(name, age, skill){  
      Father.call(this, name, age);  
      this.skill = skill; 
    } 
    Child.prototype = new Father(); 
    Child.prototype.constructor = Child; 
    Child.prototype.exam = function(){  
      console.log('i want to have an exam');
    } 
    var child = new Child('zhangsan', 20, 'dance'); 
    console.log(child.money())
    
  4. 原型式继承:将以参数形式传入的对象作为创建对象的原型。

    function creatObj(o){  
      function F(){};  
      F.prototype = o;  
      return new F(); 
    } 
    var person = {  
      name: 'zhangsan',  
      age: 20 
    } 
    var person1 = creatObj(person); 
    var person2 = creatObj(person); 
    person1.name = 'lisi'; 
    console.log(person1.name, person2.name);
    
  5. 寄生式继承

    function Father(name, age){  
      this.name = name;  
      this.age = age; 
    } 
    function Child(name, age){  
      Father.call(this, name, age); 
    } (function(){  
      var Super = function(){};  
      Super.prototype = Father.prototype;  
      Child.prototype = new Super(); 
    })() 
    Child.prototype.constructor = Child; 
    var child = new Child('Tom', 20); 
    console.log(child.name)
    

37.Js原型什么是prototype、proto、constructor? Javascript 的作用域链?

  1. 什么是prototype、proto、constructor?

    1. __proto__是原型属性,对象特有的属性,是对象指向另外一个对象(就是第二点的原型对象),一般是实例对象的属性,如arr.proto
    2. prototype是原型对象,构造函数特有的属性,是构造函数指向的对象,如Array.prototype
    3. constructor是原型对象用于指回构造函数的属性,是对象指向函数的,如Array.prototype.constructor
  2. 作用域链是保证执行函数时变量对象的有序访问,是指向变量对象的有序列表

    变量对象包含执行函数内所有的变量和函数,通过作用域链我们可以查找外部函数的变量和函数

    当我们执行函数时会首先查找执行上下文中的变量,若没有则沿着作用域链向上查找,直至全局上下文中查找全局变量。

38. 谈谈 This 对象的理解。

this是函数执行上下文中的一个属性,它指向最后一次调用该函数的对象。

  1. 函数调用时,若函数不是一个对象的属性,当其调用时this指向全局对象。
  2. 方法调用时,若方法为一个对象中的方法,调用该方法时this指向这个对象。
  3. 当函数通过构造函数用new来创建时,执行前会创建一个实例对象,这个函数的this指向该实例。
  4. 通过运用call、apply、bind方法来改变函数的this指向
    1. call方法第一个参数为改变this指向的对象,后面的参数为传递的参数
    2. apply与call的区别是传递的参数为数组
    3. bind方法与call和apply的区别是不会立即调用函数,先将函数与this指向绑定,返回改变了this指向的新函数,等到待执行时再调用。这个函数的this指向除了用new构造函数来改变,其余都不会改变。

优先级:这四种模式中,使用new构造函数的优先级最高,其次是call、apply、bind调用函数,然后是方法调用模式,最后是函数调用模式。

39. eval 是做什么的?

eval方法是将传递的字符以js语法去解析执行。

应该尽量避免使用eval语法,因为是非消耗性能,第一次解析js语法,第二次执行js语句。

// 封装一个eval方法
function eval(fn) {
  // 一个变量指向Function,防止有些前端编译工具报错
  const Fn = Function
  return new Fn('return ' + fn)()
},

40. 什么是 DOM 和 BOM?

  1. DOM是文档对象模型,它是将文档看成一个对象,这个对象主要定义了文档的方法和接口。在DOM中,文档的各个组件可以通过Object.attribute来获取,根对象是document。
  2. BOM是浏览器对象模型,它是将浏览器看成是一个对象,这个对象中定义了浏览器的方法和接口。它除了可以访问文档组件以外还可以访问浏览器窗口组件,例如导航条navigator,历史记录history等等。

41. 写一个通用的事件侦听器函数。

// event(事件)工具集
markyun.Event = {
    // 页面加载完成后
    readyEvent : function(fn) {
        if (fn==null) {
            fn=document;
        }
        var oldonload = window.onload;
        if (typeof window.onload != 'function') {
            window.onload = fn;
        } else {
            window.onload = function() {
                oldonload();
                fn();
            };
        }
    },
    // 视能力分别使用dom0||dom2||IE方式 来绑定事件
    // 参数: 操作的元素, 事件名称, 事件处理程序
    addEvent : function(element, type, handler) {
        if (element.addEventListener) {
            //事件类型、需要执行的函数、是否捕捉
            element.addEventListener(type, handler, false);
        } else if (element.attachEvent) {
            element.attachEvent('on' + type, function() {
                handler.call(element);
            });
        } else {
            element['on' + type] = handler;
        }
    },
    // 移除事件
    removeEvent : function(element, type, handler) {
        if (element.removeEventListener) {
            element.removeEventListener(type, handler, false);
        } else if (element.datachEvent) {
            element.detachEvent('on' + type, handler);
        } else {
            element['on' + type] = null;
        }
    }, 
    // 阻止事件 (主要是事件冒泡,因为IE不支持事件捕获)
    stopPropagation : function(ev) {
        if (ev.stopPropagation) {
            ev.stopPropagation();
        } else {
            ev.cancelBubble = true;
        }
    },
    // 取消事件的默认行为
    preventDefault : function(event) {
        if (event.preventDefault) {
            event.preventDefault();
        } else {
            event.returnValue = false;
        }
    },
    // 获取事件目标
    getTarget : function(event) {
        return event.target || event.srcElement;
    },
    // 获取event对象的引用,取到事件的所有信息,确保随时能使用event;
    getEvent : function(e) {
        var ev = e || window.event;
        if (!ev) {
            var c = this.getEvent.caller;
            while (c) {
                ev = c.arguments[0];
                if (ev && Event == ev.constructor) {
                    break;
                }
                c = c.caller;
            }
        }
        return ev;
    }
}; 

42. 事件是什么?IE 与火狐的事件机制有什么区别? 如何阻止冒泡?

  1. 事件是用户操作网页过程中的交互动作

    1. 例如鼠标点击事件click,鼠标移动事件mousemove等等
    2. 除了用户触发外还可以是文档加载,例如页面滚动事件scroll
    3. 事件可以封装成一个event对象,包含了事件对象的所有信息和可以对事件的操作。
  2. IE可以支持事件冒泡,火狐可以同时支持两种事件模型,即事件冒泡和事件捕获。

  3. event.stopPropogation或者ie下的event.cancelBubble = true。

43. 三种事件模型是什么?

事件是指用户操作页面过程中触发或者是浏览器触发的交互动作,有三种事件模型。

  1. DOM0事件模型,该事件模型没有事件流的概念,这种模型不会传播,它可以直接定义监听函数也可以通过js属性来定义监听函数。
  2. IE事件模型,该事件模型涉及两个事件流,执行阶段和冒泡阶段,首先会监听并触发目标事件,然后会依次冒泡到最外层document,所经过的节点依次判断是否绑定了事件,若有则触发。可以通过attachEvent来监听事件,可以监听多个函数并按顺序执行。
  3. DOM2事件模型,该事件模型涉及三个事件流,捕获阶段、执行阶段和冒泡阶段,捕获阶段为从document依次向下传播,检查每个节点是否绑定了相关事件,若有则触发。后两者与IE两个阶段相同。可以通过addEventListener来监听事件,第三个参数用来判断捕获和冒泡的顺序。

44.事件委托是什么?

事件委托的本质是通过事件冒泡使父节点能够监听到子节点的事件,从而产生事件函数。也就是将监听函数绑定在子节点的父节点上,这样不必为每个子节点都绑定监听事件,父节点可以通过事件对象定位到子节点目标上。

例如当我们动态创建子节点时,动态创建的子节点也可以有监听事件,可以利用事件委托的形式将监听函数绑定在父节点上,可以减少内存上的消耗。

45. ["1", "2", "3"].map(parseInt) 答案是多少?

答案:[1, NaN, NaN]。

parseInt方法是将数值转为整数型,接收两个参数,分别为val和radix,即数值和基数,基数范围为2~36之间,并且数值不能大于基数值,这样才能正确返回整数型。

map方法传递了三个参数,分别为value,index,array,默认第三个参数被省略。

这样数组传递给parseInt的参数分别为1-0,2-1,3-2,因为数值不能大于基数,所以后两项返回为NaN,第一项由于基数是0,所以默认为10,返回1。

46. 什么是闭包,为什么要用它?

闭包是指有权访问另一个函数内变量的函数,例如在函数内创建另一个函数,内部函数能够访问到外部函数局部变量。

闭包用途:

  1. 函数外部可以访问函数内部的变量,通过闭包函数,我们可以在函数外部调用闭包函数在外部获取到函数内部的变量。
  2. 另一个作用是将已经运行结束的函数上下文中的变量对象保存在内存中,通过闭包函数保存了对变量对象的引用,因此这个变量对象不会被回收

47. javascript 代码中的 "use strict"; 是什么意思 ? 使用它区别是什么?

use strict指的是严格模式下执行js语句

主要是消除了一些不规范的语法,提高了解析执行的效率,保证了代码的安全运行。

禁止使用with语句,不允许this指向全局对象,对象不能有重名的属性等。

48. 如何判断一个对象是否属于某个类?

  1. 通过使用instanceof运算符来判断对象构造函数的原型对象是否出现在原型链上的某个位置。
  2. Object.prototype.toString.call来返回[[Class]]属性。

49. instanceof 的作用?

instanceof运算符用于判断构造函数的原型对象是否在对象原型链上的某个位置

// 手写方法
function instance(left, right){
    var proto = Object.getPrototypeOf(left);
    var prototype = right.prototype;
    while(true){
        if(!proto) return false;
        if(proto === prototype){
            return true;
        }
        proto = Object.getPrototypeOf(proto);
    }
}

50. new 操作符具体干了什么呢?如何实现?

对于构造函数通过new创建一个新实例对象,在内存上开辟了一个新空间,同时将实例对象的proto属性指向构造函数的prototype原型对象。

并且将构造函数的属性通过this指向new创建出的实例对象。

判断函数的返回值类型,如果是值类型就返回创建的对象,如果是引用类型就返回引用类型对象

51. Javascript 中,有一个函数,执行时对象查找时,永远不会去查找原型,这个函数是?

hasOwnProperty方法,该方法用于查找对象身上自身特点的属性,而不去查找原型上的属性,会将原型上的属性忽略掉。

52. 对于 JSON 的了解?

JSON是一种基于文本的轻量级的数据交换格式,它可以被任何编程语言读取并交换数据格式。

在项目开发中我们常用JSON来进行前后端数据的传递,我们在前端将数据转换为JSON字符串的格式传递给后端,后端接收到数据后通过将其转换为特定的数据结构来进行处理。

因为JSON是基于js语法的,但两者有很大差别,JSON格式更为严格,例如不能使用方法属性,且属性用双引号。

js中提供了两种方法来对JSON格式进行处理

  1. JSON.stringify将JSON数据结构转变为JSON字符串的模式。
  2. JSON.parse方法将JSON字符串格式转变为js数据结构,若接收到的数据不是JSON字符串格式就会报错,例如我们在后端接收到JSON字符串格式的数据,可以通过JSON.parse将其转变为js数据结构再进行数据处理。

52. [].forEach.call((""),function(a){a.style.outline="1px solid #"+(~~(Math.random()(1<<24))).toString(16)}) 能解释一下这段代码的意思吗?

我们可以在控制台中通过()来获取相应的元素,类似于document.querySelectorAll()方法。这行代码的意思就是对页面中所有元素进行遍历,对每个元素设置一个outline样式,样式的颜色为一个随机颜色,Math.random()(1<<24)表示0~2^24-1之间的随机数,~表示取反,~~表示两次取反表示为取整操作,toSting(16)为转换为16进制的整数。

54. js 延迟加载的方式有哪些?

js代码在解析和执行时会阻塞页面的渲染,阻碍dom向下执行,因此我们希望能够延迟js加载,从而使页面性能更好更加流畅。 是一种优化方案

  1. 可以通过在script标签添加defer属性,表面页面自上而下执行时若遇到js脚本时不会阻塞页面向下执行,而是加载js脚本和页面解析同时进行,当页面元素全部解析完毕时再按照js脚本的顺序执行js语句。
  2. 可以通过在script标签添加async属性,它和defer属性的不同是等到js脚本加载完毕就回过头去执行js代码,而不会等到所有页面元素加载完毕,是异步进行的,js脚步不会按照顺序执行,哪个先加载完毕就先执行哪个js代码。
  3. 可以利用定时器延迟js脚本的加载。
  4. 将js脚本放在html页面的底部
  5. 可以使用动态创建script标签的方式,我们可以对文档的加载事件进行监听,当页面元素全部加载完毕时再动态创建script标签,进行外部js脚本的外链。

55. Ajax 是什么? 如何创建一个 Ajax?

Ajax属于异步通信,通过XMLHTTPRequest创建xhr,从服务器xml文档中获取数据,并更新到页面局部,不必刷新整个页面。

function ajax(options){
   //设置默认对象 
    var defaults = {
        type: 'get',
        url: '',
        data: {},
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded'
        },
        success: function(){},
        error: function(){}
    };
    //将传入参数对象与默认对象合并
    Object.assign(defaults, options);
    var params = '';
    for(var attr in defaults.data){
        params += attr + '=' + defaults.data[attr] + '&';
    }
    params = params.substr(0, params.length);
    if(defaults.type == 'get'){
        default.url = default.url + '?' + params;
    }
    var xhr = new XMLHTTPRequest();
    xhr.open(defaults.type, defaults.url);
    if(defaults.type == 'post'){
        var contentType = defaults.headers['Content-Type'];
        xhr.setResquestHeader('Content-Type', contentType);
        if(contentType == 'application/json'){
            xhr.send(JSON.stringify(defaults.data));
        } else {
            xhr.send(params);
        }    
    } else {
        xhr.send();
    }
    xhr.onload = function(){
        var contentType = xhr.getResquestHeader('Content-Type');
        var responseText = xhr.responseText;
        if(contentType.includes('application/json')){
            responseText = JSON.parse(responseText);
        }
        if(xhr.status == 200){
            defaults.success(responseText, xhr);
        } else {
            defaults.error(responseText, xhr);
        }
    }
}

56. 谈一谈浏览器的缓存机制?

浏览器的缓存机制指的是浏览器能够在一定时间内保存接收到的web资源的副本,当在有效事件内,如果浏览器再次发起相同请求,则直接从缓存中获取数据,不必再向服务器端请求。有效的缓解了服务器端压力以及加快了性能

缓存机制可以分为强缓存和协商缓存。

强缓存

在缓存有效时间内,可以直接从缓存中获取资源,不必向服务器端发起请求。

强缓存有效时间可以通过设置http头部中的expriescache-control来设置。

expries

是http1.0中的属性,它通过设置服务器端绝对时间来控制缓存的有效时间,但它的缺点是浏览器端和服务器端可能时间不一致,这就导致了缓存有效时间的误差。

cache-control

可以通过http1.1中的cache-control来控制,它提供了很多不同的控制信息

  1. max-age用来指定缓存有效最大时间,这是一个相对时间,相比于第一次浏览器端请求,过了一定时间后缓存失效。还有private用来控制缓存只能被客户端获取,不能被代理服务端获取。
  2. no-store表示资源不能被缓存
  3. no-cache表示可以被缓存但是会立即失效,每次都要向服务器端发起请求。

cache-control的优先级大于expries。

协商缓存

策略是浏览器首先向服务器端发送请求,若请求内容和条件自上次请求以来没有发生修改则返回304状态码,如果发生了修改则返回最新修改的资源。

协商缓存也可以通过两种方式来设置

last-modify

第一个是通过设置响应头中的last-modify属性,返回了资源最后一次修改时间,当浏览器再次发起请求时请求头中会带有if-modify-since属性,属性值即为last-modify的值,服务器将获取到的这个头部值与最后一次修改资源的时间进行比较,若发生修改则返回新的资源,若没有修改则告知浏览器使用缓存中的内容。

但这个方式有缺陷就是last-modify的值只能精确到秒级,如果某些资源在一秒之内修改多次,那么文件发生了修改而last-modify没有发生改变。

ETag

因此第二种方式是通过设置响应头中的ETag值,它保存了资源的唯一标识符,当资源发生修改时,ETag也会发生改变。

当浏览器端向服务器端发起请求时,会在请求头中添加if-none-match头部,值为返回的ETag值,服务器端会根据这个值与对应文件的ETag值进行对比判断是否发生了修改。

ETag的优先级会高于last-modify。

强缓存和协商缓存都是当缓存命中时直接使用缓存文件,区别是协商缓存需要先向服务器端发起一次请求

当强缓存命中时会直接使用缓存资源,若未命中则向服务器端发起请求

使用协商缓存,若协商缓存命中则告知浏览器使用缓存资源,若未命中则将最新修改过后的资源返回给浏览器端。

57. Ajax 解决浏览器缓存问题?

问题

ajax能提高页面载入的速度主要的原因是ajax能实现局部刷新,通过局部刷新机制减少了重复数据的载入,也就是说在载入数据的同时将数据缓存到内存中,一旦数据被加载其中,只要没有刷新页面,这些数据就会一直被缓存在内存中,当我们提交 的URL与历史的URL一致时,就不需要提交给服务器,也就是不需要从服务器上面去获取数据。那么,我们得到还是最开始缓存在浏览器中的数据。虽然降低了服务器的负载提高了用户的体验,但是我们不能获取最新的数据。为了保证我们读取的信息都是最新的,我们就需要禁止他的缓存功能。

解决方法

  1. 在ajax发送请求前加上 anyAjaxObj.setRequestHeader("If-Modified-Since","0")。
    1. 原理:If-Modified-Since:0 故意让缓存过期
  2. 在ajax发送请求前加上 anyAjaxObj.setRequestHeader("Cache-Control","no-cache")。
    1. 原理:直接禁用缓存机制
  3. 在URL后面加上一个随机数: "fresh=" + Math.random();。
    1. 原理:强行让每次的请求地址不同
  4. 在URL后面加上时间搓:"nowtime=" + new Date().getTime();。
    1. 原理:强行让每次的请求地址不同
  5. 如果是使用jQuery,直接这样就可以了$.ajaxSetup({cache:false})。
    1. 原理:不设置ajax缓存

58. 同步和异步的区别?

同步指的是代码自上而下按顺序执行,并且等待当前代码返回值或消息之后再继续执行下一条语句,此时程序是处于阻塞状态的,只有当前代码返回值后才能继续向下执行。

异步指的是代码不会按照同步的方式等待当前向系统请求后返回消息之后再向下执行,它会在代码请求的时候直接执行之后的语句,不会等待消息的返回,不会造成程序阻塞。等到消息返回后再处理之前异步的程序。

59. 什么是浏览器的同源政策?

同源政策指的是协议、域名以及端口号任意一个不相同则为非同源,非同源之间不能通过js获取到其他网站的cookies、localstorage等,以及不能通过js操作其他网站的DOM,并且不能通过ajax进行跨域请求。

同源政策保证了用户信息的安全,但它不限制浏览器,对于img,script等html元素不会进行同源政策限制。(跨域JSONP原理)因为这些操作不会通过响应结果而带来安全性的问题。

60. 如何解决跨域问题?

  1. JSONP方式来解决跨域请求,事先定义一个回调函数,然后通过动态创建script标签的方式并添加src属性,属性值为非同源服务器端链接。在服务器端接收到传递过来的函数名和参数信息,进行处理最终向浏览器端传递调用函数的js代码,该js代码是定义好的全局函数的调用因此会立即执行。
  2. CORS跨域请求,目前浏览器端都会支持该跨域请求,只需在服务器端的头部设置Access-Control-Allow-Origin,值为允许访问该服务器的非同源网站,若允许所有非同源网站的话,值设为*。
  3. websocket协议,该协议没有同源政策。

61. 服务器代理转发时,该如何处理 cookie?

blog.csdn.net/robertzhoux…

62. 简单谈一下 cookie ?

我理解的是cookie是服务器端创建的用于维护会话状态信息的数据,当客户端向服务器发起请求时,服务器端创建cookie并且将sessionid储存于cookies中发送给客户端,等到之后每次客户端向服务器端发起请求时,都会携带cookies用于服务器进行验证,用户是否为登录状态。cookies不能用于跨域请求

服务器端可以使用set-cookies来设置cookies信息,其中expries用于设置cookies过期时间,httponly用于禁止js脚本获取到cookies(CSRF的一种防范),只能被服务器访问。除此之外还有domain、path、secure。

63. 模块化开发怎么做?

对于模块化开发的理解是,不同模块实现了不同功能的一组方法,随着程序越来越复杂,模块化开发越来越重要。减少了维护的成本,提升了效率 。

64. js 的几种模块规范?

  1. commonJS:主要应用于服务器端,通过module.exports将模块进行导出,暴露出模块接口,通过require引入模块,实现模块的导入。commonJS是同步执行的,因为涉及到的文件方法缓存在本地磁盘中因此读取时不会发生阻碍,同步执行不会产生代码的拥堵现象。
    1. 如果module.exports是基本数据类型,和语言本身一样属于复制,在另一个模块中可以对该模块输出的变量重新赋值,并且不会影响其他模块的变量值。
    2. 对于复杂的数据类型,例如ArrayObject属于浅拷贝,即同时指向一个内存空间,因此对一个模块的值的改变可以影响另一个模块。
    3. 当使用require命令加载同一个模块时,不会再执行该模块,而是取到缓存之中的值,也就是说,CommonJS模块无论加载多少次,都会在第一次加载时运行一次,以后再加载,就返回第一次运行的结果,除非手动清除系统缓存/该模块输出为对象且改变该模块的输出中的属性值
    4. 循环加载:CommonJS属于加载时执行,即脚本代码在require时候就会全部执行,一旦出现某个模块被循环加载,只输出(此处的输出代表只在另一个模块中导入循环加载模块的已经执行的已输出内容,即exports中的变量)已经执行的部分,未执行的部分不输出
  2. AMD:如果在浏览器端采用CommonJS的模块规范,则会一直等待,直到模块加载完毕。这样就会导致浏览器处于’假死’状态。故浏览器端的模块,不能采用’同步加载’,只能采用’异步加载’。
    1. Asynchronous Module Definition(AMD):异步模块定义
    2. AMD使用时需要引入第三方的库文件:RequireJS
    3. 在浏览器端模块化开发
    4. RequireJS推广过程中对模块定义的规范化产出
    5. 推崇依赖前置:在定义模块的时候就要声明其依赖的模块,并且会立即加载其依赖的模块。
    6. 对于依赖的模块,AMD是提前执行,不过RequireJS2.0开始,也改成可以延迟执行。
    7. 适合在浏览器环境中异步加载模块,可以并行加载多个模块
    8. 提高了开发成本,并且不能按需加载,而是必须提前加载所有的依赖。
    9. AMD支持CMD写法,在写时需要引入SeaJS库文件。但更推荐是依赖前置。
    10. AMDAPI默认是一个当多个用,CMDAPI严格区分,推崇职责单一。比如AMD里,require分全局require和局部require,都叫 requireCMD里,没有全局require,而是根据模块系统的完备性,提供seajs.use来实现模块系统的加载启动。CMD里,每个API都简单纯粹。
  3. CMD:通用模块定义。它解决的问题和AMD规范是一样的,只不过在模块定义方式和模块加载时机上不同,CMD也需要额外的引入第三方的库文件,SeaJS
    1. CMDSeaJS在推广过程中对模块定义的规范化产出
    2. 推崇依赖就近,只有在用到某一模块的时候才会按需加载。
    3. 延迟执行
    4. CMDAPI严格区分,推崇职责单一
  4. ES6模块规范:使用import和export方式来输出和导入模块。
    1. ES6模块中的值属于动态只读引用,即不能在引用时改变模块的导出值。
    2. 对于只读来说,即不允许修改引入变量的值,import的变量是只读的,不论是基本数据类型还是复杂数据类型。当模块遇到import命令时,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。
    3. 对于动态来说,原始值发生变化,import加载的值也会发生变化。不论是基本数据类型还是复杂数据类型。
    4. ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量
    5. CommonJSAMD都只能在运行时确定模块之间的依赖关系。在代码1中,其实质是整体加载fs模块(即加载fs模块的全部方法),生成一个对象导出,即_fs。然后再获取其中的stat,exists,reafFile方法使用。这种加载称为“运行时加载”,因为只有运行时才能得到这个对象,导致完全没办法在编译时做“静态优化”。
    6. ES6模块化是编译时加载。由于ES6导出的不是对象,而是通过export命令显式指定输出的代码,再通过import命令输入。在代码2中,其实质是从fs模块中加载三个方法,不需要加载其中的所有方法,这种加载称为“编译时加载”或者静态加载,即 ES6 可以在编译时就完成模块加载。效率要比 CommonJS 模块的加载方式高。当然,这也导致了没法引用 ES6 模块本身,因为它不是对象。

65. AMD和 CMD 规范的区别?

  1. 对于依赖的模块,AMD 是提前执行,CMD 是延迟执行。不过 RequireJS 从 2.0 开始,也改成可以延迟执行(根据写法不同,处理方式不同)。CMD 推崇 as lazy as possible.

  2. AMD 推崇依赖前置,CMD 推崇依赖就近。看代码

    1. // AMD 
      define(['./a', './b'], function(a, b) {  // 依赖必须一开始就写好
          a.doSomething()
          ...
          b.doSomething()
          ...
      }) 
      
      // CMD
      define(function(require, exports, module) {   
          var a = require('./a')   
          a.doSomething()    
          var b = require('./b')   
          b.doSomething()    
          ... 
      })
      
      
  3. AMD 的 API 默认是一个当多个用,CMD 的 API 严格区分,推崇职责单一。比如 AMD 里,require 分全局 require 和局部 require,都叫 require。CMD 里,没有全局 require,而是根据模块系统的完备性,提供 seajs.use 来实现模块系统的加载启动。CMD 里,每个 API 都简单纯粹。

66. ES6 模块与 CommonJS 模块、AMD、CMD 的差异。

看64点

67. requireJS 的核心原理是什么?(如何动态加载的?如何避免多次加载的?如何 缓存的?)

核心原理:requireJS的核心是通过动态创建script标签的src来引入加载模块

通过正则匹配模块以及模块的依赖关系,保证文件加载的先后顺序

根据文件的路径对加载过的文件做了缓存

68. JS 模块加载器的轮子怎么造,也就是如何实现一个模块加载器?

juejin.cn/post/684490…

69. ECMAScript6 怎么写 class,为什么会出现 class 这种东西?

es6中新增的class我觉得应该是为了补充一些js中缺少的面向对象的特性,它作为一种语法糖,起始还是基于原型继承的思想,为class添加方法就等于是在构造函数的原型对象上添加方法。通过class我们可以更好的阻止代码。

70. documen.write 和 innerHTML 的区别?

document.write是改写页面结构,会代替整个文档重写整个页面。

innerHTML只是替代页面某个元素中的内容,只会重写部分元素的内容。

71. DOM 操作——怎样添加、移除、移动、复制、创建和查找节点?

  1. 创建新节点:createElement()、createTextNode()
  2. 添加、移除、替换、插入节点:Parent.appendChild(node)、Parent.removeChild(node)、Parent.replaceChild(new, old)、Parent.insertBefore(new, old)
  3. 查找节点:getElementById, getElementsByName, getElementsByTagName, getElementsByClassName, querySelector, querySelectorAll
  4. 属性操作:getAttribute, setAttribute, hasAttribute, removeAttribute

72. innerHTML 与 outerHTML 的区别?

例如对于这样一个html:

<div>我是文本<br/></div>

innerHTML内部html:我是文本

outerHTML外部html:

我是文本

73. .call() 和 .apply() 的区别?

两者作用相同,只是传入的参数形式不一样,call第一个参数为this指向对象,第二个参数之后为依次向函数内部传入的参数。apply第一个参数为this指向的对象,第二个参数为向函数传入的参数数组。

74. JavaScript类数组对象的定义?

类数组指的是拥有数组的length属性和索引下标,类数组与数组类似,但不能使用数组的方法。

可以通过以下几种方式来使类数组拥有数组的方法

  1. 通过call调用数组的slice方法来实现转换:

    Array.prototype.slice.call(arrayLike);
    
  2. 通过call调用数组的splice方法来实现转换:

    Array.prototype.splice.call(arrayLike, 0);
    
  3. 通过apply调用函数的concat方法来实现转换:

    Array.prototype.concat.apply([], arrayLike);
    
  4. 通过Array.from来实现转换:

    Array.from(arrayLike);
    

75. 数组和对象有哪些原生方法,列举一下?

  1. 数组和字符串的转换方法:toString()、join()
  2. 数组尾部操作方法:push(),pop(),push参数可以为多个
  3. 数组头部操作方法:shift(),unshift()
  4. 数组重排序的方法: reverser() sort()
  5. 数组连接的方法:concat() 返回的是拼接好的数组,不影响原数组
  6. 数组截取方法:slice(start, end) 用于截取数组中的一部分进行返回 不影响原数组
  7. 数组删除方法:splice(start, number) 用于删除数组中的指定项 返回被删除的数组,影响原数组
  8. every() some() forEach() filter() map()
  9. reduce()

76. 数组的 fill 方法?

数组的fill方法可以用一个固定值填充数组从起始索引到终止索引的全部元素。fill接收三个参数,固定值,起始索引,终止索引。其中起始索引和终止索引可省略,默认为0和this对象的length值。

array.fill(value, startIndex, endIndex)

77. [...] 的长度?

对应数组的长度

78. JavaScript 中的作用域与变量声明提升?

变量声明提升是指对变量的声明提升到了当前作用域的顶部。这是js中的作用域相关,当代码在执行前会有一个解析的过程,创建了执行上下文,初始化了一些代码执行时需要用到的对象,当访问到一个变量时会到当前作用域的执行上下文中去查找变量对象,作用域的首部就是当前执行上下文中的变量对象,包括函数的形参、所有函数和声明的变量。

79. 如何编写高性能的 Javascript ?

  1. 遵循严格模式:”use strict”;
  2. 将js脚本放在页面底部,加快渲染页面;
  3. 将js脚本成组打包,减少请求;
  4. 使用非阻塞方式下载js脚本;
  5. 尽量使用局部变量来保存全局变量;
  6. 尽量减少使用闭包;
  7. 使用window对象属性方法时省略window;
  8. 尽量减少对象成员嵌套;
  9. 缓存DOM节点的访问;
  10. 通过避免使用eval和function()构造器;
  11. 给setTimeout()和setInterval()传递函数而不是字符作为参数;
  12. 尽量使用直接量创建对象和数组;
  13. 最小化重绘(repaint)和回流(reflow);

80. 简单介绍一下 V8 引擎的垃圾回收机制

  1. 标记清除:定期对带有标记的变量进行清除,首先会将全局中所有变量进行标记,然后将被一些对象引用或者即将被引用的变量清除标记,剩下的就是等到垃圾回收机制销毁的变量。
  2. 引用计数:判断一个变量是否有对象引用它,如果没有对象引用就清除这个变量。

81. 哪些操作会造成内存泄漏?

内存泄漏指的是,系统中的内存空间不断的缩小,这是因为不断的有变量占用内存空间得不到释放。

  1. 未声明的局部变量,会产生全局变量,使这个变量一直存在于内存中无法被回收。
  2. 闭包:当不合理的使用闭包时,会造成一些变量一直留在函数中无法得到释放。
  3. 我们获取到一个DOM元素的引用,而当这个元素被删除使,一直保留着这个元素的引用,因此一直占用内存空间得不到释放。
  4. 我们若设置了定时器而没有清除它,如果定时器的循环函数一直有对外部变量的引用的话,那么这个变量会一直保存在内存中得不到释放。

82. 需求:实现一个页面操作不会整页刷新的网站,并且能在浏览器前进、后退时正确响应。给出你的技术实现方案?

popStatus + ajax。

83. 如何判断当前脚本运行在浏览器还是 node 环境中?(阿里)

this === 'window' ? 'brower' : 'node';

若全局global对象为window则在浏览器运行否则在node环境

84. 把 script 标签放在页面的最底部的 body 封闭之前和封闭之后有什么区别?浏览器会如何解析它们?

如果说放在body的封闭之前,将会阻塞其他资源的加载。 如果放在body封闭之后,不会影响body内元素的加载。

body之前的任何位置都会解析进head里边,之后的都会解析进body里边。

85. 移动端的点击事件的有延迟,时间是多久,为什么会有? 怎么解决这个延时?

移动端的点击事件有300ms的延迟,这是因为移动端有双击放大功能,有300ms的延迟是为了等待是否有第二次点击来进行屏幕放大,若300ms内没有第二次点击再认为是点击事件。解决:可以再view标签内设置禁止缩放属性,也可以设置屏幕为理想尺寸大小,同时还可以使用fastClick库。

点击穿透:是因为移动端的点击事件有300ms的延迟,touch之后300ms内响应click,这样可能会误点到元素底部的某个元素。解决方案:只用touch,若只用click的话每次点击都会有延迟现象。

86. 什么是“前端路由”?什么时候适合使用“前端路由”?“前端路由”有哪些优点和缺点?

前端路由指的是不同路由对应的不同功能的页面交给前端来做,之前是服务器通过url的不同来返回不同的页面。

一般单页面应用时候适合前端路由,大部分页面结构不改变,只改变部分结构。

前端路由优点:不必向服务器端请求,缓解了服务器端压力,用户体验好,页面流畅。

前端路由缺点:单页面应用无法记住之前滚动过得位置,也无法在前进后退过程中记住滚动的位置。

前端路由有两种实现方式,一种是hash,另一种是history.pushState。pushState为浏览器添加一条历史记录,添加完后可以使用history.state获取。并且在history模式下,前端的url必须与向后端传递的url保持一致。

87. 如何测试前端代码么? 知道 BDD, TDD, Unit Test 么? 知道怎么测试你的前端工程么(mocha, sinon, jasmin, qUnit..)?

juejin.cn/post/684490…

88. 检测浏览器版本版本有哪些方式?

第一种是window.navigator.userAgent方式来获取,但这种方式不准确因为可能会被改写。

第二种是功能检测,也是每个浏览器独有的特性来检测,例如ie下的ActiveXObject。

89. 什么是 Polyfill ?

Polyfill用于实现浏览器并不支持的原生API代码。

90. 使用 JS 实现获取文件扩展名?

  1. 利用索引下标来截取stringObject.lastIndexOf(substr) - 查找返回substr字符(串)在string里面最后一次出现的下标,找不到的话返回-1
    1. stringObject.substr(startindex[,length]) - 截取字符串,若无length参数时,直接截取到字符串末尾
    2. stringObject.substring(startIndex,[stopIndex]) - 截取字符串 (startIndex:开始下标 stop 可缺省 表示要结束的下标[该下标的字符不会被截取!])
  2. stringObject.split(sep) - 分割字符串,将字符串以指定的分隔符分割为一个数组

91. 介绍一下 js 的节流与防抖?

在页面中如果持续触发一个事件会对性能不利,例如页面滚动、鼠标移动等若持续触发会造成事件冗余,也为页面加载带来负担。

  1. 持续触发事件时,保证一定时间段内只调用一次事件处理函数。节流通俗解释就比如我们水龙头放水,阀门一打开,水哗哗的往下流,秉着勤俭节约的优良传统美德,我们要把水龙头关小点,最好是如我们心意按照一定规律在某个时间间隔内一滴一滴的往下滴。

    var throttle = function(func, delay) {
      var prev = Date.now();
      return function() {
        var context = this;
        var args = arguments;
        var now = Date.now();
        if (now - prev >= delay) {
          func.apply(context, args);
          prev = Date.now();
        }
      }
    }
    function handle() {
      console.log(Math.random());
    }
    window.addEventListener('scroll', throttle(handle, 1000));
    
    
  2. 当持续触发事件时,一定时间段内没有再触发事件,事件处理函数才会执行一次,如果设定的时间到来之前,又一次触发了事件,就重新开始延时。

    function debounce(fn, wait) {
        var timeout = null;
        return function() {
            if(timeout !== null) 
                    clearTimeout(timeout);
            timeout = setTimeout(fn, wait);
        }
    }
    // 处理函数
    function handle() {
        console.log(Math.random()); 
    }
    // 滚动事件
    window.addEventListener('scroll', debounce(handle, 1000));
    
    

92. Object.is() 与原来的比较操作符 “===”、“==” 的区别?

==表示在比较前可以进行类型转换,===表示严格比较,若类型不同会直接返回false

Object.is()与===类似,但处理了一些特殊情况,比如+0和-0不再相对,NaN和自身是相等的。

93. escape,encodeURI,encodeURIComponent 有什么区别?

  1. escape和它们不是同一类

    简单来说,escape是对字符串(string)进行编码(而另外两种是对URL),作用是让它们在所有电脑上可读。 编码之后的效果是%XX或者%uXXXX这种形式。 其中 ASCII字母 数字 @*/+ 这几个字符不会被编码,其余的都会。 最关键的是,当你需要对URL编码时,请忘记这个方法,这个方法是针对字符串使用的,不适用于URL。

  2. 最常用的encodeURI和encodeURIComponent

    对URL编码是常见的事,所以这两个方法应该是实际中要特别注意的。

    它们都是编码URL,唯一区别就是编码的字符范围,其中

    encodeURI方法不会对下列字符编码 ASCII字母 数字 ~!@#$&*()=:/,;?+'

    encodeURIComponent方法不会对下列字符编码 ASCII字母 数字 ~!*()'

    所以encodeURIComponent比encodeURI编码的范围更大。

    实际例子来说,encodeURIComponent会把 http:// 编码成 http%3A%2F%2F 而encodeURI却不会。

  3. 什么场合应该用什么方法

    1、如果只是编码字符串,不和URL有半毛钱关系,那么用escape。

    2、如果你需要编码整个URL,然后需要使用这个URL,那么用encodeURI。

    3、当你需要编码URL中的参数的时候,那么encodeURIComponent是最好方法。

94. Unicode 和 UTF-8 之间的关系?

scii、latin、gbk、Big5、unicode都是字符集,用字节来表示字符,除了ascii是1个字节表示字符外, 其它都是使用2个字节表示字符。

为了统一,国际标准化组织 ISO,制定unicode用2个字节来统一全世界所有字符。

美国人不同意,因为unicode是2个字节,相比他之前的scii的1个字节,增加了1倍的储存空间。于是国际标准化组织提出了一种方案,用UTF-8对unicode进行压缩,由2个字节压缩为1个字节。中文转换以后 3 个字节(中文比较特殊,压缩后反而占内存变3个字节,占空间增大),UTF-8 转换过程中会最终生成的是 1-6 个字节不等的数据

所以说UTF-8是Unicode的压缩,也就是编码。

unicode码是字符串类型,只存在内存中

传输(网络)或者存储(硬盘)必须进行编码,如UTF-8 / UTF-16 / UTF-32 / GBK / GB2312(字节,是二进制数据)

95. js 的事件循环是什么?

juejin.cn/post/684490…

96. js 中的深浅拷贝实现?

//浅拷贝的实现 只拷贝对象
function shallowCope(object){
    if(!object || typeof object !== 'object') return;
    //根据object的类型进行判断新建一个数组还是对象
    let newObject = Array.isArray(object) ? [] : {};
    for(let k in object){
        if(object.hasOwnProperty(k)){
            newObject[k] = object[k];
        }
    }
    return newObject;
}
//深拷贝的实现
function deepCope(object){
    if(!object || typeof object !== 'object') return;
    let newObject = Array.isArray(object) ? [] : {};
    for(let k in object){
        if(object.hasOwnProperty(k)){
            newObject[k] = 
                typeof object[k] === 'object' ? deepCope(object[k]) : object[k];
        }
    }
    return newObject;
}

97. 手写 call、apply 及 bind 函数

//手写call
Function.prototype.myCall = function(context){
    if(typeof this !== 'function'){
        console.error('type error')
    };
    //获取参数
    let args = [...arguments].slice(1),
        result = null;
    //判断context是否传入 若没传入则设为window
    context = context || window;
    //将调用函数设为对象的方法
    context.fn = this;
    result = context.fn(...args);
    delete context.fn;
    return result;
}

//手写apply
Function.prototype.myApply = function(context){
    if(typeof this !== 'function') {
        console.error('type error');
    }
    let result = null;
    context = context || window;
    context.fn = this;
    if(arguments[1]){
        result = context.fn(...arguments[1]);
    } else {
        result = context.fn()
    }
    delete context.fn;
    return result;
}

//手写bind
Function.prototype.myBind = function(newObject){
    if( typeof this !== 'function'){
        console.error('type error');
    }
    var args = [...arguments].slice(1);
    var that = this;
    return function(){
       return that.apply(newObject, args.concat([...arguments]))
    }
}

98. 函数柯里化的实现

函数柯里化是指将一种使用多个参数的函数转换为一系列单个参数函数的调用。

//手写函数柯里化
function curry(fn, args){
    //获取函数需要的总参数长度
    let leng = fn.length;
    args = args || [];
    return function(){
        let subargs = args.slice(0);
        for( let i = 0; i < arguments.length; i++){
            subargs.push(arguments[i]);
        }
        //判断此时subargs是否已经满足函数需要的参数长度需求
        if(subargs.length >= leng){
            return fn.apply(this, subargs)
        } else {
            return curry.call(this, fn, subargs);
        }
    }
}
function fn(a,b,c){
    return a+b+c;
}
var newCurry = curry(fn, 1);
newCurry(2);
newCurry(3);

99. 为什么 0.1 + 0.2 != 0.3?如何解决这个问题?

在计算机中,运算是转换为二进制再进行计算的,js是以64位双精度格式来进行计算的,只有53位有效数字,之后的数字会被截掉,因此产生了误差。

所以解决0.1 + 0.2 != 0.3的办法,用原生提供的方式就是

parseFloat((0.1 + 0.2).toFixed(10)) === 0.3 // true

100. 原码、反码和补码的介绍

原码是计算机中对数字的二进制的定点表示方法,首位为符号位,其余为数值位。

正数的反码和补码都和原码一样,负数的反码首位为1,数值位为原码取反,补码为反码加1

后言

因为字数限制 只能分为上下!
JS基础下篇

在这必须提到的借鉴文章-
牛客网大神