面试-JS

282 阅读9分钟

注意reduce每个阶段的返回值

[1,2,3,4].reduce((x, y) => console.log(x, y))

reduce函数接收4个参数累计器、当前值、当前索引值、源数组,数组不传初始化值,则表明执行时以1位累计器2位当前值,且由于console.log(x, y)没有返回值,所以后面的累计器都为```undefined``

参数默认值可以为计算其它参数提供基础

const add = (x) => x + x;
function myFun(num = 2, value = add(num)) {
    consolo.log(num, value)
}
myFun()
myFun(3)

Object.freeze的特点

const person = {
    name: 'lili',
    address: {
        street: '100 Main st'
    }
}
Object.freeze(person)

person.address.street = '200 Main st' // 会产生副作用的原因
// 使用递归进行深冻结

Object.freeze对一个对象进行冻结后,不能对其属性增删改。但只是浅冻结,所以会有副作用产生

对象增加添加迭代器属性

const person = {
    name: 'lili',
    age: 1
}
[...person] // 实现输出结果为['lili', 1]需要怎么做?
[Symbol.iterator]() {
    const self = this;
    const keys = Object.keys(self);
    let index = 0;
    return {
        next() {
            if (index < keys.length) {
                return {
                    value: self[keys[index++]],
                    done: false
                };
            } else {
                return { value: undefined, done: true };
            }
        }
    };
}

parseInt识别到非法字符时会停止解析

const num = parseInt('7*6',10)
console.log(num)

是非法字符,所以只能解析到7

require为运行时加载,import为编译时加

剩余参数只能放在最后一位

defindProperty定义的参数是否可以通过Object.keys()的方式遍历出来

var o = {}; // 创建一个新对象

// 在对象中添加一个属性与数据描述符的示例
Object.defineProperty(o, "a", {
  value : 37,
  writable : true, // 写
  enumerable : true, // 可枚举
  configurable : true // 该属性描述符才能够被改变,同时该属性也能从对应的对象上被删除。默认为 false。
});

var bValue;
Object.defineProperty(o, "b", {
  get : function(){
    return bValue;
  },
  set : function(newValue){
    bValue = newValue;
  },
  enumerable : true,
  configurable : true
});

Obejct.seal可以防止新属性被增加或者旧属性被减少,但是可以改,区别freeze

const person = { name: 'lili' }
Obejct.seal(person)

模板字符串

function getPersonInfo(a, b, c) {
    console.log(a)
    console.log(b)
    console.log(c)
}
const name = 'dd'
const age = 18
getPersonInfo`${name} is ${age} old`

,第一个参数始终是字符串数组,其余参数获取传递到模板字符串中的表达式的值,其余参数传递到字符串模板中

十进制二进制互转

十进制转换为二进制

var num = 100;
num.toString(2);

二进制转换为十进制

var num = 1100100;
parseInt(num,2);

翻转一个字符串

let str="hello word";
let b=[...str].reverse().join("");//drow olleh

如何对一个数组去重

let unique= [...new Set(array)];
//es6 Set数据结构类似于数组,成员值是唯一的,有重复的值会自动去重。
//Set内部使用===来判断是否相等,类似'1'和1会两个都保存,NaN和NaN只会保存一个

实现一个 LazyMan

继承

ES5的继承时可以通过prototype和构造函数机制来实现。1、ES5的继承实质上是先创建子类的实例对象,2、然后再将父类的方法添加到this上。

具体的:ES6通过class关键字定义类,里面有构造方法,类之间通过extends关键字实现继承。子类必须在constructor方法中调用super方法,否则新建实例报错。因为子类没有自己的this对象,而是继承了父类的this对象,然后对其进行加工。如果不调用super方法,子类得不到this对象。

super关键字指代父类的实例,即父类的this对象。在子类构造函数中,调用super后,才可使用this关键字,否则报错。

ES5是使this指向父类,而ES6是继承父类的this

ES5 实现继承

function father(name) {
  this.name = name;
  this.code = function() {
    console.log(this.name+' coding');
  }
}
father.prototype.add = function(food) {
  console.log(this.name+' eat');
}
function son(name){
  father.call(this);
  this.name = name;
}
son.prototype = Object.create(father.prototype);
son.prototype.constructor = son;
var sonVar= new son('faker');
console.log(sonVar.code());
console.log(sonVar.add())

ES6 实现继承

class father {
    constructor(name) {
        this.name = name
    }
    code() {
        console.log(this.name+' coding');
    }
    add() {
        console.log(this.name+' eat');
    }
}
class son extends father {
    constructor(name) {
        super(name)
    }
}
var sonVar= new son('faker');
console.log(sonVar.code());
console.log(sonVar.add())

介绍 js 的基本数据类型。

string number boolean undefinded null bigint symbol

ES6 中新增的 Symbol 类型,代表创建后独一无二且不可变的数据类型,它的出现我认为主要是为了解决可能出现的全局变量冲突的问题

JavaScript 有几种类型的值?你能画一下他们的内存图吗?

js可以分为两类,基础类型和复杂类型,基础类型存储在栈中,复杂类型存储在堆中

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

在操作系统中,内存被分为栈区和堆区,栈区由编译器自动释放,堆区又程序员释放

标准内置对象的分类

例如 Number、Math、Date、String、RegExp、Map、Set、WeakMap、WeakSet、 Promise、Generator、Reflect、Proxy

null 和 undefined 的区别?

undefined 代表的含义是未定义,null 代表的含义是空对象。

js 获取原型的方法?

(1)p.__proto__
(2)p.constructor.prototype
(3)Object.getPrototypeOf(p)

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

(1)以 0X、0x 开头的表示为十六进制。
(2)以 0、0O、0o 开头的表示为八进制。
(3)以 0B、0b 开头的表示为二进制格式。

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

Number.MIN_SAFE_INTEGER ~ Number.MAX_SAFE_INTEGER
-Infinity ~ Infinity

typeof NaN 的结果是什么?

typeof NaN; // "number"

isNaN 和 Number.isNaN 函数的区别?

函数isNaN接收参数后,会尝试将这个参数转换为数值,任何不能被转换为数值的的值都会返回 true,因此非数字值传入也会返回 true ,会影响 NaN 的判断。

函数 Number.isNaN 会首先判断传入参数是否为数字,如果是数字再继续判断是否为 NaN ,这种方法对于 NaN 的判断更为准确。

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

创建出来的只是一个空数组,只不过它的 length 属性被设置成了指定的值。

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

{} 的 valueOf 结果为 {} ,toString 的结果为 "[object Object]"
[] 的 valueOf 结果为 [] ,toString 的结果为 ""

~ 操作符的作用?

~运算符的作用和Math.floor()的作用一样,对于正数相当于向下取整,但是效率比Math.floor()高,对于负数则是向上取整。

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

解析允许字符串(如 parseInt())中含有非数字字符,解析按从左到右的顺序,如果遇到非数字字符就停止。而转换(如 Number ())不允许出现非数字字符,否则会失败并返回 NaN。

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

(1)字符串和数字之间的相等比较,将字符串转换为数字之后再进行比较。
(2)其他类型和布尔类型之间的相等比较,先将布尔值转换为数字后,再应用其他规则进行比较。
(3)null 和 undefined 之间的相等比较,结果为真。其他值和它们进行比较都返回假值。
(4)如果一个操作值为 NaN ,则相等比较返回 false( NaN 本身也不等于 NaN )。
(5)如果两个操作值都是对象,则比较它们是不是指向同一个对象。如果两个操作数都指向同一个对象,则相等操作符返回
 true,否则,返回 false

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

(1)使用 Number() 方法,前提是所包含的字符串不包含不合法字符。
(2)使用 parseInt() 方法,parseInt() 函数可解析一个字符串,并返回一个整数。还可以设置要解析的数字的基数。
 当基数的值为 0,或没有设置该参数时,parseInt() 会根据 string 来判断数字的基数。
(3)使用 parseFloat() 方法,该函数解析一个字符串参数并返回一个浮点数。
(4)使用 + 操作符的隐式转换。

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

function format(number){
    return number && number.replace(/(?!^)(?=(\d{3})+\.)/g,",")
}

常用正则表达式

手机号码正则
var reg = /^1[34578]\d{9}$/

匹配日期,如 yyyy-mm-dd 格式
var reg = /^\d{4}-(0[0-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$/

邮箱
var reg = /^[a-zA-Z0-9_-+\@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/

身份证
var reg = /(^\d{18}$)|((^\d{17}[Xx]$)/

生成随机数的各种方法?

(1)使用数组 sort 方法对数组元素随机排序,让 Math.random() 出来的数与 0.5 比较,如果大于就返回 1 交换位置,如果小于就返回 -1,不交换位置。

 function randomSort(a, b) { 
   return Math.random() > 0.5 ? -1 : 1; 
 }
 
 缺点:每个元素被派到新数组的位置不是随机的,原因是 sort() 方法是依次比较的。
 
(3)随机交换数组内的元素(洗牌算法类似)
function randomSort(array) {
    let len = array.length
    for (let index = 0;index < len-1; index++) {
        let randomIndex = Math.floor(Math.random() * (len - index)) + index
        [array[index], array[randomIndex]] = [array[randomIndex], array[index]] 
    }
    return array;
}

javascript 创建对象的几种方式?

工厂模式 构造函数模式 原型模式 寄生式组合继承

寄生式组合继承的实现?

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

Person.prototype.sayName = function () {
    console.log("My name is " + this.name + ".");
}

function Student(name, grade) {
    Person.call(this, name);
    this.grade = grade;
}

Student.prototype = Object.create(Person.prototype);
Student.prototype.constructor = Student;

Student.prototype.sayMyGrade = function () {
    console.log("My grade is " + this.grade + ".");
} 

Javascript 的作用域链?

保证对执行环境通过作用域链能有序访问所有变量和函数。

谈谈 This 对象的理解。

this 是执行上下文中的一个属性,它指向最后一次调用这个方法的对象。在实际开发中,this 的指向可以通过四种调用模式来判断。
(1)第一种是函数调用模式,当一个函数不是一个对象的属性时,直接作为函数来调用时,this 指向全局对象。
(2)第二种是方法调用模式,如果一个函数作为一个对象的方法来调用时,this 指向这个对象。
(3)第三种是构造器调用模式,如果一个函数用 new 调用时,函数执行前会新创建一个对象,this 指向这个新创建的对象。
(4)第四种是 apply、call 和 bind 调用模式,这三个方法都可以显示的指定调用函数的 this 指向。其中 apply方法接收两个参数:一个是 this 绑定的对象,一个是参数数组。call 方法接收的参数,第一个是 this 绑定的对象,后面的其余参数是传入函数执行的参数。也就是说,在使用 call() 方法时,传递给函数的参数必须逐个列举出来。bind 方法通过传入一个对象,返回一个 this 绑定了传入对象的新函数。这个函数的 this 指向除了使用 new 时会被改变,其他情况下都不会改变。

eval 是做什么的?

对应的字符串解析成 JS 代码并运行。

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

const eventUtils = {
    addEvent: function(element, type, handler) {
        if (element.addEventListener) {
            element.addEventListener(type, handler, false)
        } else if (element.attachEvent) {
            element.attachEvent("on"+type, handler)
        } else {
            element["on"+type] = handler
        }
    },
    removeEvent: function(element, type, handler) {
        if (element.removeEventListener) {
            element.removeEventListener(type, handler, false)
        } else if (element.detachEvent) {
            element.detachEvent("on" + type, handler);
        } else {
            element["on"+type] = null
        }
    },
    // 获取事件目标
    getTarget: function(event) {
        return event.target || event.srcElement;
    },
    // 获取 event 对象的引用,取到事件的所有信息,确保随时能使用 event
    getEvent: function(event) {
        return event || window.event;
    },
    // 阻止事件(主要是事件冒泡,因为 IE 不支持事件捕获)
    stopPropagation: function(event) {
        if (event.stopPropagation) {
            event.stopPropagation()
        } else {
            event.cancelBubble()
        }
    },
    // 取消事件的默认行为
    preventDefault: function() {
        if (event.preventDefault) {
            event.preventDefault()
        } else {
            event.returnValue = false;
        }
    },
}

事件委托是什么?

利用浏览器的冒泡机制,因为事件在冒泡过程中会上传到父节点,父节点可以通过事件对象获取到目标节点,统一处理多个子元素的事件

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

1,NaN,NaN
字符串的值不能大于基数,因此后面两次调用均失败,返回 NaN ,第一次基数为 0 ,按十进制解析返回 1。

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

什么是闭包:
闭包是指有权访问另一个函数作用域中变量的函数
为什么要用它:
1.使我们在函数外部能够返回到函数内部的变量
2.使已经结束运行的函数的上下文中的变量继续保留在内存中

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

Object.prototype.toString()

instanceof 的作用?

instanceof用于判断构造函数的prototype属性是否出现在出现在对象的原型属性链上

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

1、通过arguments获取构造函数
2、新建一个空对象,对象的原型指向构造函数的原型
3、构造函数的this指向新建的对象,并执行函数
4、判断是否有返回值,有则返回,没有则返回新对象
function createNew(Fn, ...args) {
    this.obj = {}
    Object.setPrototypeOf(this.obj, Fn.prototype)
    let result = Fn.apply(this.obj, args)
    return result instanceof Object ? result : this.obj
}

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

hasOwnProperty所有继承了 Object 的对象都会继承到hasOwnProperty方法。
这个方法可以用来检测一个对象是否含有特定的自身属性,和in 运算符不同,该方法会忽略掉那些从原型链上继承到的属性。

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

defer属性
async属性
setTimeout
动态创建DOM方式
让JS最后加载

js 的加载、解析和执行会阻塞页面的渲染过程,因此我们希望 js 脚本能够尽可能的延迟加载,提高页面的渲染速度。
我了解到的几种方式是:
第一种方式是我们一般采用的是将 js 脚本放在文档的底部,来使 js 脚本尽可能的在最后来加载执行。
第二种方式是给 js 脚本添加defer属性,这个属性会让脚本的加载与文档的解析同步解析,然后在文档解析完成后再执行这个脚本文件,这样的话就能使页面的渲染不被阻塞。多个设置了defer属性的脚本按规范来说最后是顺序执行的,但是在一些浏览器中可能不是这样。
第三种方式是给 js 脚本添加async属性,这个属性会使脚本异步加载,不会阻塞页面的解析过程,但是当脚本加载完成后立即执行 js 脚本,这个时候如果文档没有解析完成的话同样会阻塞。多个async属性的脚本的执行顺序是不可预测的,一般不会按照代码的顺序依次执行。
第四种方式是动态创建 DOM标签的方式,我们可以对文档的加载事件进行监听,当文档加载完成后再动态的创建 script 标签来引入 js 脚本。

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

具体来说,AJAX 包括以下几个步骤。
(1)创建 XMLHttpRequest 对象,也就是创建一个异步调用对象
(2)创建一个新的 HTTP 请求,并指定该 HTTP 请求的方法、URL 及验证信息
(3)设置响应 HTTP 请求状态变化的函数
(4)发送 HTTP 请求
(5)获取异步调用返回的数据
(6)使用 JavaScript 和 DOM 实现局部刷新

一般实现:
const SERVER_URL = "/server"

// 创建 XMLHttpRequest 对象
let xhr = new XMLHttpResquest();

// 创建 Http 请求
xhr.open("GET", SERVER_URL, true);

// 设置响应 HTTP 请求状态变化的函数
xhr.onreadystatechange = function() {
    if (this.readyState !== 4) return
    // 当请求成功时
    if (this.status === 200) {
        handle(this.response);
    } else {
     console.error(this.statusText);
    }
}

// 设置请求失败时的监听函数
xhr.onerror = function () {
    console.error(this.statusText);
}

// 设置请求头信息
xhr.responseType = "json";
xhr.setRequestHeader("Accept", "application/json");

// 发送 Http 请求
xhr.send(null);

我对 ajax 的理解是,它是一种异步通信的方法,通过直接由 js 脚本向服务器发起 http 通信,然后根据服务器返回的数据,
更新网页的相应部分,而不用刷新整个页面的一种方法。

创建一个 ajax 有这样几个步骤

首先是创建一个 XMLHttpRequest 对象。

然后在这个对象上使用 open 方法创建一个 http 请求,open 方法所需要的参数是请求的方法、请求的地址、是否异步和用户
的认证信息。

在发起请求前,我们可以为这个对象添加一些信息和监听函数。比如说我们可以通过 setRequestHeader 方法来为请求添加头信
息。我们还可以为这个对象添加一个状态监听函数。一个 XMLHttpRequest 对象一共有 5 个状态,当它的状态变化时会触发on
readystatechange 事件,我们可以通过设置监听函数,来处理请求成功后的结果。当对象的 readyState 变为 4 的时候,代
表服务器返回的数据接收完成,这个时候我们可以通过判断请求的状态,如果状态是 2xx 或者 304 的话则代表返回正常。这个时
候我们就可以通过 response 中的数据来对页面进行更新了。

当对象的属性和监听函数设置完成后,最后我们调用 sent 方法来向服务器发起请求,可以传入参数作为发送的数据体。

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

浏览器的缓存机制指的是通过在一段时间内保留已接收到的 web 资源的一个副本,如果在资源的有效时间内,发起了对这个资源的
再一次请求,那么浏览器会直接使用缓存的副本,而不是向服务器发起请求。使用 web 缓存可以有效地提高页面的打开速度,减少
不必要的网络带宽的消耗。

web 资源的缓存策略一般由服务器来指定,可以分为两种,分别是强缓存策略和协商缓存策略。

使用强缓存策略时,如果缓存资源有效,则直接使用缓存资源,不必再向服务器发起请求。强缓存策略可以通过两种方式来设置,分
别是 http 头信息中的 Expires 属性和 Cache-Control 属性。

服务器通过在响应头中添加 Expires 属性,来指定资源的过期时间。在过期时间以内,该资源可以被缓存使用,不必再向服务器发
送请求。这个时间是一个绝对时间,它是服务器的时间,因此可能存在这样的问题,就是客户端的时间和服务器端的时间不一致,或
者用户可以对客户端时间进行修改的情况,这样就可能会影响缓存命中的结果。

Expires 是 http1.0 中的方式,因为它的一些缺点,在 http 1.1 中提出了一个新的头部属性就是 Cache-Control 属性,
它提供了对资源的缓存的更精确的控制。它有很多不同的值,常用的比如我们可以通过设置 max-age 来指定资源能够被缓存的时间
的大小,这是一个相对的时间,它会根据这个时间的大小和资源第一次请求时的时间来计算出资源过期的时间,因此相对于 Expires
来说,这种方式更加有效一些。常用的还有比如 private ,用来规定资源只能被客户端缓存,不能够代理服务器所缓存。还有如 n
o-store ,用来指定资源不能够被缓存,no-cache 代表该资源能够被缓存,但是立即失效,每次都需要向服务器发起请求。

一般来说只需要设置其中一种方式就可以实现强缓存策略,当两种方式一起使用时,Cache-Control 的优先级要高于 Expires 。

使用协商缓存策略时,会先向服务器发送一个请求,如果资源没有发生修改,则返回一个 304 状态,让浏览器使用本地的缓存副本。
如果资源发生了修改,则返回修改后的资源。协商缓存也可以通过两种方式来设置,分别是 http 头信息中的 Etag 和 Last-Mod
ified 属性。

服务器通过在响应头中添加 Last-Modified 属性来指出资源最后一次修改的时间,当浏览器下一次发起请求时,会在请求头中添
加一个 If-Modified-Since 的属性,属性值为上一次资源返回时的 Last-Modified 的值。当请求发送到服务器后服务器会通
过这个属性来和资源的最后一次的修改时间来进行比较,以此来判断资源是否做了修改。如果资源没有修改,那么返回 304 状态,让
客户端使用本地的缓存。如果资源已经被修改了,则返回修改后的资源。使用这种方法有一个缺点,就是 Last-Modified 标注的最
后修改时间只能精确到秒级,如果某些文件在1秒钟以内,被修改多次的话,那么文件已将改变了但是 Last-Modified 却没有改变,
这样会造成缓存命中的不准确。

因为 Last-Modified 的这种可能发生的不准确性,http 中提供了另外一种方式,那就是 Etag 属性。服务器在返回资源的时候,
在头信息中添加了 Etag 属性,这个属性是资源生成的唯一标识符,当资源发生改变的时候,这个值也会发生改变。在下一次资源请
求时,浏览器会在请求头中添加一个 If-None-Match 属性,这个属性的值就是上次返回的资源的 Etag 的值。服务接收到请求后
会根据这个值来和资源当前的 Etag 的值来进行比较,以此来判断资源是否发生改变,是否需要返回资源。通过这种方式,比 Last
-Modified 的方式更加精确。

当 Last-Modified 和 Etag 属性同时出现的时候,Etag 的优先级更高。使用协商缓存的时候,服务器需要考虑负载平衡的问题,
因此多个服务器上资源的 Last-Modified 应该保持一致,因为每个服务器上 Etag 的值都不一样,因此在考虑负载平衡时,最好
不要设置 Etag 属性。

强缓存策略和协商缓存策略在缓存命中时都会直接使用本地的缓存副本,区别只在于协商缓存会向服务器发送一次请求。它们缓存不命
中时,都会向服务器发送请求来获取资源。在实际的缓存机制中,强缓存策略和协商缓存策略是一起合作使用的。浏览器首先会根据请
求的信息判断,强缓存是否命中,如果命中则直接使用资源。如果不命中则根据头信息向服务器发起请求,使用协商缓存,如果协商缓
存命中的话,则服务器不返回资源,浏览器直接使用本地资源的副本,如果协商缓存不命中,则浏览器返回最新的资源给浏览器。

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

我对浏览器的同源政策的理解是,一个域下的 js 脚本在未经允许的情况下,不能够访问另一个域的内容。这里的同源的指的是两个
域的协议、域名、端口号必须相同,否则则不属于同一个域。

同源政策主要限制了三个方面

第一个是当前域下的 js 脚本不能够访问其他域下的 cookie、localStorage 和 indexDB。

第二个是当前域下的 js 脚本不能够操作访问操作其他域下的 DOM。

第三个是当前域下 ajax 无法发送跨域请求。

同源政策的目的主要是为了保证用户的信息安全,它只是对 js 脚本的一种限制,并不是对浏览器的限制,对于一般的 img、或者
script 脚本请求都不会有跨域的限制,这是因为这些操作都不会通过响应结果来进行可能出现安全问题的操作。

如何解决跨域问题?

通过jsonp
跨域资源共享CORS
postMessage
nginx代理
node中间键代理
WebSocket 协议跨域
document.domain + iframe 跨域
location.hash + iframe
window.name + iframe 跨域

解决跨域的方法我们可以根据我们想要实现的目的来划分。

首先我们如果只是想要实现主域名下的不同子域名的跨域操作,我们可以使用设置 document.domain 来解决。

(1)将 document.domain 设置为主域名,来实现相同子域名的跨域操作,这个时候主域名下的 cookie 就能够被子域名所访问。
 同时如果文档中含有主域名相同,子域名不同的 iframe 的话,我们也可以对这个 iframe 进行操作。

如果是想要解决不同跨域窗口间的通信问题,比如说一个页面想要和页面的中的不同源的 iframe 进行通信的问题,我们可以使用 
location.hash 或者 window.name 或者 postMessage 来解决。

(2)使用 location.hash 的方法,我们可以在主页面动态的修改 iframe 窗口的 hash 值,然后在 iframe 窗口里实现监听
 函数来实现这样一个单向的通信。因为在 iframe 是没有办法访问到不同源的父级窗口的,所以我们不能直接修改父级窗口的 
 hash 值来实现通信,我们可以在 iframe 中再加入一个 iframe ,这个 iframe 的内容是和父级页面同源的,所以我们可
 以 window.parent.parent 来修改最顶级页面的 src,以此来实现双向通信。

(3)使用 window.name 的方法,主要是基于同一个窗口中设置了 window.name 后不同源的页面也可以访问,所以不同源的子页
 面可以首先在 window.name 中写入数据,然后跳转到一个和父级同源的页面。这个时候级页面就可以访问同源的子页面中 wi
 ndow.name 中的数据了,这种方式的好处是可以传输的数据量大。

(4)使用 postMessage 来解决的方法,这是一个 h5 中新增的一个 api。通过它我们可以实现多窗口间的信息传递,通过获取到
 指定窗口的引用,然后调用 postMessage 来发送信息,在窗口中我们通过对 message 信息的监听来接收信息,以此来实现不
 同源间的信息交换。

如果是像解决 ajax 无法提交跨域请求的问题,我们可以使用 jsonp、cors、websocket 协议、服务器代理来解决问题。
 
(5)使用 jsonp 来实现跨域请求,它的主要原理是通过动态构建 script  标签来实现跨域请求,因为浏览器对 script 标签的
 引入没有跨域的访问限制 。通过在请求的 url 后指定一个回调函数,然后服务器在返回数据的时候,构建一个 json 数据的
包装,这个包装就是回调函数,然后返回给前端,前端接收到数据后,因为请求的是脚本文件,所以会直接执行,这样我们先前
 定义好的回调函数就可以被调用,从而实现了跨域请求的处理。这种方式只能用于 get 请求。

(6)使用 CORS 的方式,CORS 是一个 W3C 标准,全称是"跨域资源共享"。CORS 需要浏览器和服务器同时支持。目前,所有浏览
 器都支持该功能,因此我们只需要在服务器端配置就行。浏览器将 CORS 请求分成两类:简单请求和非简单请求。对于简单请求
 ,浏览器直接发出 CORS 请求。具体来说,就是会在头信息之中,增加一个 Origin 字段。Origin 字段用来说明本次请求来
 自哪个源。服务器根据这个值,决定是否同意这次请求。对于如果 Origin 指定的源,不在许可范围内,服务器会返回一个正常
 的 HTTP 回应。浏览器发现,这个回应的头信息没有包含 Access-Control-Allow-Origin 字段,就知道出错了,从而抛
 出一个错误,ajax 不会收到响应信息。如果成功的话会包含一些以 Access-Control- 开头的字段。

 非简单请求,浏览器会先发出一次预检请求,来判断该域名是否在服务器的白名单中,如果收到肯定回复后才会发起请求。

(7)使用 websocket 协议,这个协议没有同源限制。

(8)使用服务器来代理跨域的访问请求,就是有跨域的请求操作时发送请求给后端,让后端代为请求,然后最后将获取的结果发返回。

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

我的理解是 cookie 是服务器提供的一种用于维护会话状态信息的数据,通过服务器发送到浏览器,浏览器保存在本地,当下一次
有同源的请求时,将保存的cookie 值添加到请求头部,发送给服务端。这可以用来实现记录用户登录状态等功能。cookie 一般
可以存储 4k 大小的数据,并且只能够被同源的网页所共享访问。

服务器端可以使用 Set-Cookie 的响应头部来配置 cookie 信息。一条 cookie 包括了5个属性值 expires、domain、path、
secure、HttpOnly。其中 expires 指定了 cookie 失效的时间,domain 是域名、path是路径,domain 和 path 一起限
制了 cookie 能够被哪些 url 访问。secure 规定了 cookie 只能在确保安全的情况下传输,HttpOnly 规定了这个 cookie
只能被服务器访问,不能使用 js 脚本访问。

在发生 xhr 的跨域请求的时候,即使是同源下的 cookie,也不会被自动添加到请求头部,除非显示地规定。

js 的几种模块规范?

js 中现在比较成熟的有四种模块加载方案。
第一种是 CommonJS 方案,它通过 require 来引入模块,通过 module.exports 定义模块的输出接口。这种模块加载方案是
服务器端的解决方案,它是以同步的方式来引入模块的,因为在服务端文件都存储在本地磁盘,所以读取非常快,所以以同步的方式
加载没有问题。但如果是在浏览器端,由于模块的加载是使用网络请求,因此使用异步加载的方式更加合适。

第二种是 AMD 方案,这种方案采用异步加载的方式来加载模块,模块的加载不影响后面语句的执行,所有依赖这个模块的语句都定
义在一个回调函数里,等到加载完成后再执行回调函数。require.js 实现了 AMD 规范。

第三种是 CMD 方案,这种方案和 AMD 方案都是为了解决异步模块加载的问题,sea.js 实现了 CMD 规范。它和 require.js
的区别在于模块定义时对依赖的处理不同和对依赖模块的执行时机的处理不同。

第四种方案是 ES6 提出的方案,使用 import 和 export 的形式来导入导出模块。这种方案和上面三种方案都不同。

AMD 和 CMD 规范的区别?

它们之间的主要区别有两个方面。

(1)第一个方面是在模块定义时对依赖的处理不同。AMD 推崇依赖前置,在定义模块的时候就要声明其依赖的模块。而 CMD 推崇
就近依赖,只有在用到某个模块的时候再去 require。

(2)第二个方面是对依赖模块的执行时机处理不同。首先 AMD 和 CMD 对于模块的加载方式都是异步加载,不过它们的区别在于
模块的执行时机,AMD 在依赖模块加载完成后就直接执行依赖模块,依赖模块的执行顺序和我们书写的顺序不一定一致。而 CMD
在依赖模块加载完成后并不执行,只是下载而已,等到所有的依赖模块都加载好后,进入回调函数逻辑,遇到 require 语句
的时候才执行对应的模块,这样模块的执行顺序就和我们书写的顺序保持一致了。

// CMD
define(function(require, exports, module) {
    var a = require('./a')
    a.doSomething()
    // 此处略去 100 行
    var b = require('./b') // 依赖可以就近书写
    b.doSomething()
    // ...
})

// AMD 默认推荐
define(['./a', './b'], function(a, b) { // 依赖必须一开始就写好
    a.doSomething()
    // 此处略去 100 行
    b.doSomething()
    // ...
})

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

(1)CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。CommonJS 模块输出的是值的拷贝,也就是说,一
 旦输出一个值,模块内部的变化就影响不到这个值。ES6 模块的运行机制与 CommonJS 不一样。JS 引擎对脚本静态分析
 的时候,遇到模块加载命令 import,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那
 个模块里面去取值。

(2)CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。CommonJS 模块就是对象,即在输入时是先加载整个模块,
 生成一个对象,然后再从这个对象上面读取方法,这种加载称为“运行时加载”。而 ES6 模块不是对象,它的对外接口只是
 一种静态定义,在代码静态解析阶段就会生成。

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

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

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

在我看来 ES6 新添加的 class 只是为了补充 js 中缺少的一些面向对象语言的特性,但本质上来说它只是一种语法糖,不是
一个新的东西,其背后还是原型继承的思想。通过加入 class 可以有利于我们更好的组织代码。

在 class 中添加的方法,其实是添加在类的原型上的。

documen.write 和 innerHTML 的区别?

document.write 的内容会代替整个文档内容,会重写整个页面。

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

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

(1)创建新节点
createDocumentFragment
createElement
createTextNode
(2)添加、移除、替换、插入
appendChild
removeChild
replaceChild
insertChild
(3)查找
getElementById 
getElementsByName 
getElementsByTagName
getElementsByClassName
querySelector
querySelectorAll
(4)属性操作
getAttribute(key)
setAttribute(key,value)
hasAttribute(key)
removeAttribute(key)  

innerHTML 与 outerHTML 的区别?

对于这样一个 HTML 元素:<div>content<br/></div>。

innerHTML:内部 HTML,content<br/>;
outerHTML:外部 HTML,<div>content<br/></div>;
innerText:内部文本,content ;
outerText:内部文本,content ;

JavaScript 类数组对象的定义?

一个拥有 length 属性和若干索引属性的对象就可以被称为类数组对象,类数组对象和数组类似,但是不能调用数组的方法。

常见的类数组对象有 arguments 和 DOM 方法的返回结果,还有一个函数也可以被看作是类数组对象,因为它含有 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);

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

数组和字符串的转换方法:toString()、toLocalString()、join() 其中 join() 方法可以指定转换为字符串时的分
隔符。

数组尾部操作的方法 pop() 和 push(),push 方法可以传入多个参数。

数组首部操作的方法 shift() 和 unshift()

重排序的方法 reverse() 和 sort(),sort() 方法可以传入一个函数来进行比较,传入前后两个值,如果返回值为正数,
则交换两个参数的位置。

数组连接的方法 concat() ,返回的是拼接好的数组,不影响原数组。

数组截取办法 slice(),用于截取数组中的一部分返回,不影响原数组。

数组插入方法 splice(),影响原数组

查找特定项的索引的方法,indexOf() 和 lastIndexOf()

迭代方法 every()、some()、filter()、map() 和 forEach() 方法

数组归并方法 reduce() 和 reduceRight() 方法

数组的 fill 方法?

fill() 方法用一个固定值填充一个数组中从起始索引到终止索引内的全部元素。不包括终止索引。
fill 方法接受三个参数 value,start 以及 end,start 和 end 参数是可选的,其默认值分别为 0 和 this 对象的 
length 属性值。

[,,,] 的长度?

尾后逗号 (有时叫做“终止逗号”)在向 JavaScript 代码添加元素、参数、属性时十分有用。如果你想要添加新的属性,并
且上一行已经使用了尾后逗号,你可以仅仅添加新的一行,而不需要修改上一行。这使得版本控制更加清晰,以及代码维护麻烦
更少。

JavaScript 一开始就支持数组字面值中的尾后逗号,随后向对象字面值(ECMAScript 5)中添加了尾后逗号。最近(ECMAS
cript 2017),又将其添加到函数参数中。但是 JSON 不支持尾后逗号。

如果使用了多于一个尾后逗号,会产生间隙。 带有间隙的数组叫做稀疏数组(密致数组没有间隙)。稀疏数组的长度为逗号的数
量。

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

v8 的垃圾回收机制基于分代回收机制,这个机制又基于世代假说,这个假说有两个特点,一是新生的对象容易早死,另一个是不死
的对象会活得更久。基于这个假说,v8 引擎将内存分为了新生代和老生代。

新创建的对象或者只经历过一次的垃圾回收的对象被称为新生代。经历过多次垃圾回收的对象被称为老生代。

新生代被分为 From 和 To 两个空间,To 一般是闲置的。当 From 空间满了的时候会执行 Scavenge 算法进行垃圾回收。当我
们执行垃圾回收算法的时候应用逻辑将会停止,等垃圾回收结束后再继续执行。这个算法分为三步:

(1)首先检查 From 空间的存活对象,如果对象存活则判断对象是否满足晋升到老生代的条件,如果满足条件则晋升到老生代。如
果不满足条件则移动 To 空间。

(2)如果对象不存活,则释放对象的空间。

(3)最后将 From 空间和 To 空间角色进行交换。

新生代对象晋升到老生代有两个条件:

(1)第一个是判断是对象否已经经过一次 Scavenge 回收。若经历过,则将对象从 From 空间复制到老生代中;若没有经历,则
复制到 To 空间。

(2)第二个是 To 空间的内存使用占比是否超过限制。当对象从 From 空间复制到 To 空间时,若 To 空间使用超过 25%,则对
象直接晋升到老生代中。设置 25% 的原因主要是因为算法结束后,两个空间结束后会交换位置,如果 To 空间的内存太小,会
影响后续的内存分配。

老生代采用了标记清除法和标记压缩法。标记清除法首先会对内存中存活的对象进行标记,标记结束后清除掉那些没有标记的对象。由
于标记清除后会造成很多的内存碎片,不便于后面的内存分配。所以了解决内存碎片的问题引入了标记压缩法。

由于在进行垃圾回收的时候会暂停应用的逻辑,对于新生代方法由于内存小,每次停顿的时间不会太长,但对于老生代来说每次垃圾回
收的时间长,停顿会造成很大的影响。 为了解决这个问题 V8 引入了增量标记的方法,将一次停顿进行的过程分为了多步,每次执行
完一小步就让运行逻辑执行一会,就这样交替运行。

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

(1)意外的全局变量
(2)被遗忘的计时器或回调函数
(3)脱离 DOM 的引用
(4)闭包
第一种情况是我们由于使用未声明的变量,而意外的创建了一个全局变量,而使这个变量一直留在内存中无法被回收。

第二种情况是我们设置了 setInterval 定时器,而忘记取消它,如果循环函数有对外部变量的引用的话,那么这个变量会被一直留
在内存中,而无法被回收。

第三种情况是我们获取一个 DOM 元素的引用,而后面这个元素被删除,由于我们一直保留了对这个元素的引用,所以它也无法被回
收。

第四种情况是不合理的使用闭包,从而导致某些变量一直被留在内存当中。

假设有一个利用ajax来实现分页效果的页面,当点击第3页的时候,浏览器中就会显示第3页的内容。但此时浏览器地址栏中仍然是第1页的地址,点击浏览器后退按键会发现根本无法回到第1页,如果刷新浏览器,则又会回到第1页。需求:实现一个页面操作不会整页刷新的网站,并且能在浏览器前进、后退时正确响应。给出你的技术实现方案?

popstate pushState replaceState
pushState push一条记录,更改页面url,但是不刷新页面。
popstate 监听变化
replaceState 更新路由记录,但是不刷新页面。

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

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

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

移动端点击有 300ms 的延迟是因为移动端会有双击缩放的这个操作,因此浏览器在 click 之后要等待 300ms,看用户有没有下一次
点击,来判断这次操作是不是双击。

有三种办法来解决这个问题

(1)通过 meta 标签禁用网页的缩放。
(2)通过 meta 标签将网页的 viewport 设置为 ideal viewport。
(3)调用一些 js 库,比如 FastClick

click 延时问题还可能引起点击穿透的问题,就是如果我们在一个元素上注册了 touchStart 的监听事件,这个事件会将这个元素隐
藏掉,我们发现当这个元素隐藏后,触发了这个元素下的一个元素的点击事件,这就是点击穿透。

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

// 防抖
const debounce = (fn, delay) => {
    let timer = null
    let self = this
    return (...args) => {
        clearTimeout(timer)
        timer = setTimeout(() => {
            fn.apply(self, args)
        }, delay)
    }
}
// 节流
const throttle = (fn, delay) => {
    let startTime = new Date().getTime()
    return (...args) => {
        let nowTime = new Date().getTime()
        if (nowTime - startTime >= delay) {
            fn.apply(self, args)
            startTime = nowTime
        } 
    }
}

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

两等号判等,会在比较时进行类型转换。
三等号判等(判断严格),比较时不进行隐式类型转换,(类型不同则会返回false)。

Object.is 在三等号判等的基础上特别处理了 NaN 、-0 和 +0 ,保证 -0 和 +0 不再相同,但 Object.is(NaN, NaN) 会
返回 true.

escape,encodeURI,encodeURIComponent 有什么区别?

encodeURI 是对整个 URI 进行转义,将 URI 中的非法字符转换为合法字符,所以对于一些在 URI 中有特殊意义的字符不会进行
转义。

encodeURIComponent 是对 URI 的组成部分进行转义,所以一些特殊字符也会得到转义。 

escape 和 encodeURI 的作用相同,不过它们对于 unicode 编码为 0xff 之外字符的时候会有区别,escape 是直接在字符的
unicode 编码前加上 %u,而 encodeURI 首先会将字符转换为 UTF-8 的格式,再在每个字节前加上 %。

js 的事件循环是什么?

因为js是单下单线程运行,在代码运行时将不同函数的执行上下文压入栈中来保证函数的正常执行。在执行时,如果遇到异步事件,js引擎不会一直等待其返回结果,而是将这个时间挂起,继续执行栈中的其它任务。单异步事件执行完毕后,再讲异步事件对应的回调加入到任务队列中等待执行,任务队列可分为宏任务队列和微任务队列,当当前执行栈中的事件执行完毕后,js首先会对微任务队列是否有任务可以执行,如果有就将微任务压入栈中,当微任务对列中的任务都执行完成后再去判断宏任务对列中的任务,如果有则执行宏任务。

微任务:promise process.nextTick MutationObserver
宏任务:script脚本的执行 setTimeout setInerval,还有如 I/O 操作、UI 渲
 染等。

js 中的深浅拷贝实现?

const deepCopy = (object, map = new WeakMap()) => {
    // 只拷贝对象
    if (!object || typeof object !== 'object') return
    // 看看是否是循环引用
    if (map.get(object)) {
        return map.get(object)
    }
    // 根据 object 的类型判断是新建一个数组还是对象
    let cloneData = Array.isArray(object) ? [] : {}
    // 遍历 object,并且判断是 object 的属性才拷贝
    for (let key in object) {
        if (typeof object[key] === 'object') {
            map.set(object[key], object[key])
            cloneData[key] = deepCopy(object[key], map)
        } else {
            cloneData[key] = object[key]
        }
    }
    return cloneData
}

手写 call、apply 及 bind 函数

// call
Function.prototype.myCall = function(context = window) {
    // 这个this指向的是我们的函数
    context.fn = this
    // 获取参数
    const args = [...arguments].slice(1)
    const result = context.fn(...args)
    delete context.fn
    return result
}

// apply
Function.prototype.myApply = function(context = window) {
    // 这个this指向的是我们的函数
    context.fn = this
    let result
    if (arguments[1]) {
        result = context.fn(...arguments[1])
    } else {
        result = context.fn()
    }
    delete context.fn
    return result
}

// bind.prototype
Function.prototype.myBind = function(context = window) {
    // 这个this指向的是我们的函数
    let fn = this
    let context = arguments[0] || window
    // 一次传参
    // const args = Array.prototype.slice.call(arguments, 1)
    let args = [...arguments].slice(1)
    return function F() {
        // 二次传参
        let args2 = [...arguments].slice(1)
        if (this instanceof F) {
            // 构造函数的形式进行调用
            return new fn(...args.concat[args2])
        } else {
            // 普通函数调用
            return fn.apply(context, ...args.concat[args2])
        }
    }
}

函数柯里化的实现

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

0.1 和 0.2 在转换为二进制表示的时候
会出现位数无限循环的情况。js 中是以 64 位双精度格式来存储数字的,只有 53 位的有效数字,超过这个长度的位数会被截取掉
这样就造成了精度丢失的问题。

原码、反码和补码的介绍

toPrecision 和 toFixed 和 Math.round 的区别?

toPrecision 用于处理精度,精度是从左至右第一个不为 0 的数开始数起。
toFixed 是对小数点后指定位数取整,从小数点开始数起。
Math.round 是将一个数字四舍五入到一个整数。
Math.ceil 向上取整
Math.floor 向下取整

什么是 XSS 攻击?如何防范 XSS 攻击?

XSS 攻击指的是跨站脚本攻击,是一种代码注入攻击。攻击者通过在网站注入恶意脚本,使之在用户的浏览器上运行,从而盗取用户
的信息如 cookie 等。

XSS 的本质是因为网站没有对恶意代码进行过滤,与正常的代码混合在一起了,浏览器没有办法分辨哪些脚本是可信的,从而导致了
恶意代码的执行。

 XSS 一般分为存储型、反射型和 DOM 型。

存储型指的是恶意代码提交到了网站的数据库中,当用户请求数据的时候,服务器将其拼接为 HTML 后返回给了用户,从而导致了恶
意代码的执行。

反射型指的是攻击者构建了特殊的 URL,当服务器接收到请求后,从 URL 中获取数据,拼接到 HTML 后返回,从而导致了恶意代码
的执行。

DOM 型指的是攻击者构建了特殊的 URL,用户打开网站后,js 脚本从 URL 中获取数据,从而导致了恶意代码的执行。

XSS 攻击的预防可以从两个方面入手,一个是恶意代码提交的时候,一个是浏览器执行恶意代码的时候。

对于第一个方面,如果我们对存入数据库的数据都进行的转义处理,但是一个数据可能在多个地方使用,有的地方可能不需要转义,由
于我们没有办法判断数据最后的使用场景,所以直接在输入端进行恶意代码的处理,其实是不太可靠的。

因此我们可以从浏览器的执行来进行预防,一种是使用纯前端的方式,不用服务器端拼接后返回。另一种是对需要插入到 HTML 中的
代码做好充分的转义。对于 DOM 型的攻击,主要是前端脚本的不可靠而造成的,我们对于数据获取渲染和字符串拼接的时候应该对可
能出现的恶意代码情况进行判断。

还有一些方式,比如使用 CSP ,CSP 的本质是建立一个白名单,告诉浏览器哪些外部资源可以加载和执行,从而防止恶意代码的注
入攻击。

还可以对一些敏感信息进行保护,比如 cookie 使用 http-only ,使得脚本无法获取。也可以使用验证码,避免脚本伪装成用户
执行一些操作。

什么是 CSP?

CSP 指的是内容安全策略,它的本质是建立一个白名单,告诉浏览器哪些外部资源可以加载和执行。我们只需要配置规则,如何拦截
由浏览器自己来实现。

通常有两种方式来开启 CSP,一种是设置 HTTP 首部中的 Content-Security-Policy,一种是设置 meta 标签的方式 <meta
http-equiv="Content-Security-Policy">

什么是 CSRF 攻击?如何防范 CSRF 攻击?

CSRF 攻击指的是跨站请求伪造攻击,攻击者诱导用户进入一个第三方网站,然后该网站向被攻击网站发送跨站请求。如果用户在被
攻击网站中保存了登录状态,那么攻击者就可以利用这个登录状态,绕过后台的用户验证,冒充用户向服务器执行一些操作。

CSRF 攻击的本质是利用了 cookie 会在同源请求中携带发送给服务器的特点,以此来实现用户的冒充。

一般的 CSRF 攻击类型有三种:

第一种是 GET 类型的 CSRF 攻击,比如在网站中的一个 img 标签里构建一个请求,当用户打开这个网站的时候就会自动发起提
交。

第二种是 POST 类型的 CSRF 攻击,比如说构建一个表单,然后隐藏它,当用户进入页面时,自动提交这个表单。

第三种是链接类型的 CSRF 攻击,比如说在 a 标签的 href 属性里构建一个请求,然后诱导用户去点击。

CSRF 可以用下面几种方法来防护:

第一种是同源检测的方法,服务器根据 http 请求头中 origin 或者 referer 信息来判断请求是否为允许访问的站点,从而对
请求进行过滤。当 origin 或者 referer 信息都不存在的时候,直接阻止。这种方式的缺点是有些情况下 referer 可以被伪
造。还有就是我们这种方法同时把搜索引擎的链接也给屏蔽了,所以一般网站会允许搜索引擎的页面请求,但是相应的页面请求这种
请求方式也可能被攻击者给利用。

第二种方法是使用 CSRF Token 来进行验证,服务器向用户返回一个随机数 Token ,当网站再次发起请求时,在请求参数中加
入服务器端返回的 token ,然后服务器对这个 token 进行验证。这种方法解决了使用 cookie 单一验证方式时,可能会被冒
用的问题,但是这种方法存在一个缺点就是,我们需要给网站中的所有请求都添加上这个 token,操作比较繁琐。还有一个问题是
一般不会只有一台网站服务器,如果我们的请求经过负载平衡转移到了其他的服务器,但是这个服务器的 session 中没有保留这
个 token 的话,就没有办法验证了。这种情况我们可以通过改变 token 的构建方式来解决。

第三种方式使用双重 Cookie 验证的办法,服务器在用户访问网站页面时,向请求域名注入一个Cookie,内容为随机字符串,然
后当用户再次向服务器发送请求的时候,从 cookie 中取出这个字符串,添加到 URL 参数中,然后服务器通过对 cookie 中的
数据和参数中的数据进行比较,来进行验证。使用这种方式是利用了攻击者只能利用 cookie,但是不能访问获取 cookie 的特点
。并且这种方法比 CSRF Token 的方法更加方便,并且不涉及到分布式访问的问题。这种方法的缺点是如果网站存在 XSS 漏洞的
,那么这种方式会失效。同时这种方式不能做到子域名的隔离。

第四种方式是使用在设置 cookie 属性的时候设置 Samesite ,限制 cookie 不能作为被第三方使用,从而可以避免被攻击者
利用。Samesite 一共有两种模式,一种是严格模式,在严格模式下 cookie 在任何情况下都不可能作为第三方 Cookie 使用,
在宽松模式下,cookie 可以被请求是 GET 请求,且会发生页面跳转的请求所使用。

SQL 注入攻击?

SQL 注入攻击指的是攻击者在 HTTP 请求中注入恶意的 SQL 代码,服务器使用参数构建数据库 SQL 命令时,恶意 SQL 被一起构
造,破坏原有 SQL 结构,并在数据库中执行,达到编写程序时意料之外结果的攻击行为。

什么是 MVVM?比之 MVC 有什么区别?什么又是 MVP ?

vue 双向数据绑定原理?

Object.defineProperty 介绍?

Object.defineProperty 函数一共有三个参数,第一个参数是需要定义属性的对象,第二个参数是需要定义的属性,第三个是该
属性描述符。

一个属性的描述符有四个属性,分别是 value 属性的值,writable 属性是否可写,enumerable 属性是否可枚举,configura
ble 属性是否可配置修改。

使用 Object.defineProperty() 来进行数据劫持有什么缺点?

有一些对属性的操作,使用这种方法无法拦截,比如说通过下标方式修改数组数据或者给对象新增属性,vue 内部通过重写函数解决
了这个问题。在 Vue3.0 中已经不使用这种方式了,而是通过使用 Proxy 对对象进行代理,从而实现数据劫持。使用 Proxy 的
好处是它可以完美的监听到任何方式的数据改变,唯一的缺点是兼容性的问题,因为这是 ES6 的语法。

什么是 Virtual DOM?为什么 Virtual DOM 比原生 DOM 快?

比较新旧对象树,并把差异更新到真实DOM,减少了回流和重绘

如何比较两个 DOM 树的差异?

两棵树完全diff算法的复杂度是O(n^3),前端中很少跨层移动元素,所以只需要比较同一层的元素,这样复杂度就变成O(n)
先比较同级再比下一级,逐层递进

什么是 requestAnimationFrame ?

按肉眼频率执行的函数
跟着浏览器的绘制走,如果浏览设备绘制间隔是16.7ms,那我就这个间隔绘制;如果浏览设备绘制间隔是10ms, 我就10ms绘制。这样就不会存在过度绘制的问题,动画不会掉帧
  1. 谈谈你对 webpack 的看法
我认为webpack的主要原理是,它将所有资源看成模块,把页面当成一个整体,通过给定的入口,webpack 从入口文件开始,找到所有的依赖文件,将各个依赖文件模块通过 loader 和 plugins 处理后,然后打包在一起,最
 后输出一个浏览器可识别的 JS 文件。
Webpack 具有四个核心的概念,分别是 Entry(入口)、Output(输出)、loader 和 Plugins(插件)。

Entry 是 webpack 的入口起点,它指示 webpack 应该从哪个模块开始着手,来作为其构建内部依赖图的开始。

Output 属性告诉 webpack 在哪里输出它所创建的打包文件,也可指定打包文件的名称,默认位置为 ./dist。

loader 可以理解为 webpack 的编译器,它使得 webpack 可以处理一些非 JavaScript 文件,将其转成js模块

Plugins可以用于执行范围更广的任务,包括打包、优化、压缩、搭建服务器等等。

如何实现add柯里化

function add(a, b, c) {  
  return a + b + c;
}
function curry(fn, args) {  
    let len = fn.length;  // 待柯里化的函数的参数长度
    let tempArgs = args || [];  
    return function() {    
        tempArgs = tempArgs.concat([...arguments])    
        if (tempArgs.length < len) {      
            return curry.call(this, fn, tempArgs);    
        } else {      
            return fn.apply(this, tempArgs);    
        }  
    };
}
var sum = curry(add);

sum(1)(2,3)   // 6
sum(1)(2)(3)   // 6

offsetWidth/offsetHeight,clientWidth/clientHeight 与 scrollWidth/scrollHeight 的区别?

clientWidth/clientHeight 返回的是元素的内部宽度,它的值只包含 content + padding,如果有滚动条,不包含滚动条。
clientTop 返回的是上边框的宽度。
clientLeft 返回的左边框的宽度。

offsetWidth/offsetHeight 返回的是元素的布局宽度,它的值包含 content + padding + border 包含了滚动条。
offsetTop 返回的是当前元素相对于其 offsetParent 元素的顶部的距离。
offsetLeft 返回的是当前元素相对于其 offsetParent 元素的左部的距离。

scrollWidth/scrollHeight 返回值包含 content + padding + 溢出内容的尺寸。
scrollTop 属性返回的是一个元素的内容垂直滚动的像素数。
scrollLeft 属性返回的是元素滚动条到元素左边的距离。

异步编程的实现方式?

回调函数
优点:简单、容易理解
缺点:不利于维护,代码耦合高

事件监听(采用时间驱动模式,取决于某个事件是否发生):
优点:容易理解,可以绑定多个事件,每个事件可以指定多个回调函数
缺点:事件驱动型,流程不够清晰

发布/订阅(观察者模式)
类似于事件监听,但是可以通过‘消息中心’,了解现在有多少发布者,多少订阅者

Promise 对象
优点:可以利用 then 方法,进行链式写法;可以书写错误时的回调函数;
缺点:编写和理解,相对比较难

Generator 函数
优点:函数体内外的数据交换、错误处理机制
缺点:流程管理不方便

async 函数
优点:内置执行器、更好的语义、更广的适用性、返回的是 Promise、结构清晰。
缺点:错误处理机制

js 中的异步机制可以分为以下几种

第一种最常见的是使用回调函数的方式,使用回调函数的方式有一个缺点是,多个回调函数嵌套的时候会造成回调函数地狱,上下两
层的回调函数间的代码耦合度太高,不利于代码的可维护。

第二种是 Promise 的方式,使用 Promise 的方式可以将嵌套的回调函数作为链式调用。但是使用这种方法,有时会造成多个 t
hen 的链式调用,可能会造成代码的语义不够明确。

第三种是使用 generator 的方式,它可以在函数的执行过程中,将函数的执行权转移出去,在函数外部我们还可以将执行权转移回
来。当我们遇到异步函数执行的时候,将函数执行权转移出去,当异步函数执行完毕的时候我们再将执行权给转移回来。因此我们在 
generator 内部对于异步操作的方式,可以以同步的顺序来书写。使用这种方式我们需要考虑的问题是何时将函数的控制权转移回
来,因此我们需要有一个自动执行 generator 的机制,比如说 co 模块等方式来实现 generator 的自动执行。

第四种是使用 async 函数的形式,async 函数是 generator 和 promise 实现的一个自动执行的语法糖,它内部自带执行器,
当函数内部执行到一个 await 语句的时候,如果语句返回一个 promise 对象,那么函数将会等待 promise 对象的状态变为 r
esolve 后再继续向下执行。因此我们可以将异步逻辑,转化为同步的顺序来书写,并且这个函数可以自动执行。

Js 动画与 CSS 动画区别及相应实现

CSS3 的动画的优点

在性能上会稍微好一些,浏览器会对 CSS3 的动画做一些优化
代码相对简单

缺点

在动画控制上不够灵活
兼容性不好

JavaScript 的动画正好弥补了这两个缺点,控制能力很强,可以单帧的控制、变换,同时写得好完全可以兼容 IE6,并且功能强
大。对于一些复杂控制的动画,使用 javascript 会比较靠谱。而在实现一些小的交互动效的时候,就多考虑考虑 CSS 吧

get 请求传参长度的误区

实际上 HTTP 协议从未规定 GET/POST 的请求长度限制是多少。对 get 请求参数的限制是来源与浏览器或web 服务器,浏览器
或 web 服务器限制了 url 的长度。

URL 和 URI 的区别?

URI: Uniform Resource Identifier      指的是统一资源标识符
URL: Uniform Resource Location        指的是统一资源定位符
URN: Universal Resource Name          指的是统一资源名称

get 和 post 请求在缓存方面的区别

缓存一般只适用于那些不会更新服务端数据的请求。一般 get 请求都是查找请求,不会对服务器资源数据造成修改,而 post 请
求一般都会对服务器数据造成修改,所以,一般会对 get 请求进行缓存,很少会对 post 请求进行缓存。

图片的懒加载和预加载

懒加载也叫延迟加载,指的是在长网页中延迟加载图片的时机,当用户需要访问时,再去加载,这样可以提高网站的首屏加载速度,提升用户的体验,并且可以减少服务器的压力。它适用于图片很多,页面很长的电商网站的场景。懒加载的实现原理是,将页面上的图片的 src 属性设置为空字符串,将图片的真实路径保存在一个自定义属性中,当页面滚动的时候,进行判断,如果图片进入页面可视区域内,则从自定义属性中取出真实路径赋值给图片的 src 属性,以此来实现图片的延迟加载。

预加载指的是将所需的资源提前请求加载到本地,这样后面在需要用到时就直接从缓存取资源。通过预加载能够减少用户的等待时间,提高用户的体验。我了解的预加载的最常用的方式是使用 js 中的 image 对象,通过为 image 对象来设置 src 属性,来实现图片的预加载。

这两种方式都是提高网页性能的方式,两者主要区别是一个是提前加载,一个是迟缓甚至不加载。懒加载对服务器前端有一定的缓解压力作用,预加载则会增加服务器前端压力。

mouseover 和 mouseenter 的区别?

当鼠标移动到元素上时就会触发 mouseenter 事件,类似 mouseover,它们两者之间的差别是 mouseenter 不会冒泡。

由于 mouseenter 不支持事件冒泡,导致在一个元素的子元素上进入或离开的时候会触发其 mouseover 和 mouseout 事件,但是却不会触发 mouseenter 和 mouseleave 事件。

js 拖拽功能的实现

var drag = document.getElementById('drag');
var diffX
var diffY
var isMouseDown = false
drag.addEventListener('mousedown', function(e) {
  isMouseDown = true
  diffX = e.clientX - drag.offsetLeft
  diffY = e.clientX - drag.offsetTop
}, false)
drag.addEventListener('mouseup', function(e) {
  isMouseDown = false
  drag.removeEventListener('mouseup', function() {
    
  }, false)     
  drag.removeEventListener('mousemove', function() {
    
  }, false)
}, false)
drag.addEventListener('mousemove', function(e) {
  var left = e.clientX-diffX;
	var top = e.clientY-diffY;
  //控制拖拽物体的范围只能在浏览器视窗内,不允许出现滚动条
  if (left < 0) {
    left = 0;
  } else if (left > window.innerWidth-drag.offsetWidth) {
    left = window.innerWidth-drag.offsetWidth;
  }
  if(top < 0){
    top = 0;
  }else if(top > window.innerHeight-drag.offsetHeight) {
    top = window.innerHeight-drag.offsetHeight;
  }

  //移动时重新得到物体的距离,解决拖动时出现晃动的现象
  if (isMouseDown) {
    drag.style.left = left+ 'px';
    drag.style.top = top + 'px';
  }
}, false)

let 和 const 的注意点?

1.块级作用域
2.不能重复声明
3.暂时性死区
4.不存在变量提升

什么是 rest 参数?

rest 参数(形式为...变量名),用于获取函数的多余参数。

Symbol 类型的注意点?

1.Symbol 函数前不能使用 new 命令,否则会报错。
2.Symbol 函数可以接受一个字符串作为参数,表示对 Symbol 实例的描述,主要是为了在控制台显示,或者转为字符串时,比较容易区分。
3.Symbol 作为属性名,该属性不会出现在 for...in、for...of 循环中,也不会被 Object.keys()、Object.getOwnPropertyNames()、JSON.stringify() 返回。
4.Object.getOwnPropertySymbols 方法返回一个数组,成员是当前对象的所有用作属性名的 Symbol 值。
5.Symbol.for 接受一个字符串作为参数,然后搜索有没有以该参数作为名称的 Symbol 值。如果有,就返回这个 Symbol 值,否则就新建并返回一个以该字符串为名称的 Symbol 值。
6.Symbol.keyFor 方法返回一个已登记的 Symbol 类型值的 key。

Set 和 WeakSet 结构?

1.ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。
2.WeakSet 结构与 Set 类似,也是不重复的值的集合。但是 WeakSet 的成员只能是对象,而不能是其他类型的值。WeakSet 中的对象都是弱引用,即垃圾回收机制不考虑 WeakSet 对该对象的引用,

Map 和 WeakMap 结构?

1.Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。
2.WeakMap 结构与 Map 结构类似,也是用于生成键值对的集合。但是 WeakMap 只接受对象作为键名( null 除外),不接受其他类型的值作为键名。而且 WeakMap 的键名所指向的对象,不计入垃圾回收机制。

什么是 Proxy ?

Proxy 用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”,即对编程语言进行编程。

Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。

Reflect 对象创建目的?

1.将 Object 对象的一些明显属于语言内部的方法(比如 Object.defineProperty,放到 Reflect 对象上。
2.修改某些 Object 方法的返回结果,让其变得更合理。
3.让 Object 操作都变成函数行为。
4.Reflect 对象的方法与 Proxy 对象的方法一一对应,只要是 Proxy 对象的方法,就能在 Reflect 对象上找到对应的方法。这就让 Proxy 对象可以方便地调用对应的 Reflect 方法,完成默认行为,作为修改行为的基础。也就是说,不管 Proxy 怎么修改默认行为,你总可以在 Reflect 上获取默认行为。

什么是 Promise 对象,什么是 Promises/A 规范?

Promise 是一个构造函数,接收一个函数作为参数,返回一个 Promise 实例。一个 Promise 实例有三种状态,分别是 pending、resolved 和 rejected,分别代表了进行中、已成功和已失败。实例的状态只能由 pending 转变 resolved 或者 rejected 状态,并且状态一经改变,就凝固了,无法再被改变了。状态的改变是通过 resolve() 和 reject() 函数来实现的,我们
可以在异步操作结束后调用这两个函数改变 Promise 实例的状态,它的原型上定义了一个 then 方法,使用这个 then 方法可以为两个状态的改变注册回调函数。这个回调函数属于微任务,会在本轮事件循环的末尾执行。

手写一个 Promise

const PENDING = "pending";
const RESOLVED = "resolved";
const REJECTED = "rejected";

function MyPromise(fn) {
  // 保存初始化状态
  var self = this;

  // 初始化状态
  this.state = PENDING;

  // 用于保存 resolve 或者 rejected 传入的值
  this.value = null;

  // 用于保存 resolve 的回调函数
  this.resolvedCallbacks = [];

  // 用于保存 reject 的回调函数
  this.rejectedCallbacks = [];

  // 状态转变为 resolved 方法
  function resolve(value) {
    // 判断传入元素是否为 Promise 值,如果是,则状态改变必须等待前一个状态改变后再进行改变
    if (value instanceof MyPromise) {
      return value.then(resolve, reject);
    }

    // 保证代码的执行顺序为本轮事件循环的末尾
    setTimeout(() => {
      // 只有状态为 pending 时才能转变,
      if (self.state === PENDING) {
        // 修改状态
        self.state = RESOLVED;

        // 设置传入的值
        self.value = value;

        // 执行回调函数
        self.resolvedCallbacks.forEach(callback => {
          callback(value);
        });
      }
    }, 0);
  }

  // 状态转变为 rejected 方法
  function reject(value) {
    // 保证代码的执行顺序为本轮事件循环的末尾
    setTimeout(() => {
      // 只有状态为 pending 时才能转变
      if (self.state === PENDING) {
        // 修改状态
        self.state = REJECTED;

        // 设置传入的值
        self.value = value;

        // 执行回调函数
        self.rejectedCallbacks.forEach(callback => {
          callback(value);
        });
      }
    }, 0);
  }

  // 将两个方法传入函数执行
  try {
    fn(resolve, reject);
  } catch (e) {
    // 遇到错误时,捕获错误,执行 reject 函数
    reject(e);
  }
}

MyPromise.prototype.then = function(onResolved, onRejected) {
  // 首先判断两个参数是否为函数类型,因为这两个参数是可选参数
  onResolved =
    typeof onResolved === "function"
      ? onResolved
      : function(value) {
          return value;
        };

  onRejected =
    typeof onRejected === "function"
      ? onRejected
      : function(error) {
          throw error;
        };

  // 如果是等待状态,则将函数加入对应列表中
  if (this.state === PENDING) {
    this.resolvedCallbacks.push(onResolved);
    this.rejectedCallbacks.push(onRejected);
  }

  // 如果状态已经凝固,则直接执行对应状态的函数

  if (this.state === RESOLVED) {
    onResolved(this.value);
  }

  if (this.state === REJECTED) {
    onRejected(this.value);
  }
};

怎么做 JS 代码 Error 统计?

window.onerror异常处理
window.onerror无法捕获资源异常的错误,因为网络请求异常不会事件冒泡
所以我们一般不用window.onerror,而采用window.addEventListener('error',callback)
tips: 如何区分是捕获的异常还是资源错误,可以通过instanceof区分,捕获的异常instanceof是ErrorEvent, 而资源错误instanceof是Event
promise异常无法用onerror或 try-catch捕获。可以监听unhandledrejection事件
window.addEventListener("unhandledrejection", function(e){
    e.preventDefault()
    console.log(e.reason);
    return true;
});

单例模式模式是什么?

单例模式保证了全局只有一个实例来被访问。比如说常用的如弹框组件的实现和全局状态的实现。

策略模式是什么?

策略模式主要是用来将方法的实现和方法的调用分离开,外部通过不同的参数可以调用不同的策略。我主要在 MVP 模式解耦的时候
用来将视图层的方法定义和方法调用分离。

代理模式是什么?

代理模式是为一个对象提供一个代用品或占位符,以便控制对它的访问。比如说常见的事件代理。

中介者模式是什么?

中介者模式指的是,多个对象通过一个中介者进行交流,而不是直接进行交流,这样能够将通信的各个对象解耦。

适配器模式是什么?

适配器用来解决两个接口不兼容的情况,不需要改变已有的接口,通过包装一层的方式实现两个接口的正常协作。假如我们需要一种
新的接口返回方式,但是老的接口由于在太多地方已经使用了,不能随意更改,这个时候就可以使用适配器模式。比如我们需要一种
自定义的时间返回格式,但是我们又不能对 js 时间格式化的接口进行修改,这个时候就可以使用适配器模式。

Vue 的各个生命阶段是什么?

Vue 一共有8个生命阶段,分别是创建前、创建后、加载前、加载后、更新前、更新后、销毁前和销毁后,每个阶段对应了一个生命周期的钩子函数。

(1)beforeCreate 钩子函数,在实例初始化之后,在数据监听和事件配置之前触发。因此在这个事件中我们是获取不到 data 数据的。

(2)created 钩子函数,在实例创建完成后触发,此时可以访问 data、methods 等属性。但这个时候组件还没有被挂载到页面中去,所以这个时候访问不到 $el 属性。一般我们可以在这个函数中进行一些页面初始化的工作,比如通过 ajax 请求数据来对页面进行初始化。

(3)beforeMount 钩子函数,在组件被挂载到页面之前触发。在 beforeMount 之前,会找到对应的 template,并编译成 render 函数。

(4)mounted 钩子函数,在组件挂载到页面之后触发。此时可以通过 DOM API 获取到页面中的 DOM 元素。

(5)beforeUpdate 钩子函数,在响应式数据更新时触发,发生在虚拟 DOM 重新渲染和打补丁之前,这个时候我们可以对可能会被移除的元素做一些操作,比如移除事件监听器。

(6)updated 钩子函数,虚拟 DOM 重新渲染和打补丁之后调用。

(7)beforeDestroy 钩子函数,在实例销毁之前调用。一般在这一步我们可以销毁定时器、解绑全局事件等。

(8)destroyed 钩子函数,在实例销毁之后调用,调用后,Vue 实例中的所有东西都会解除绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。

当我们使用 keep-alive 的时候,还有两个钩子函数,分别是 activated 和 deactivated 。用 keep-alive 包裹的组件在切换时不会进行销毁,而是缓存到内存中并执行 deactivated 钩子函数,命中缓存渲染后会执行 actived 钩子函数。

Vue 组件间的参数传递方式?

props emit
$refs
$parent $children
provider inject
eventBus
vuex

computed 和 watch 的差异?

vue-router 中的导航钩子函数

(1)全局的钩子函数 beforeEach 和 afterEach

beforeEach 有三个参数,to 代表要进入的路由对象,from 代表离开的路由对象。next 是一个必须要执行的函数,如果不传参数,那就执行下一个钩子函数,如果传入 false,则终止跳转,如果传入一个路径,则导航到对应的路由,如果传入 error ,则导航终止,error 传入错误的监听函数。

(2)单个路由独享的钩子函数 beforeEnter,它是在路由配置上直接进行定义的。

(3)组件内的导航钩子主要有这三种:beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave。它们是直接在路由组
件内部直接进行定义的。

route和router 的区别?

$route 是“路由信息对象”,包括 path,params,hash,query,fullPath,matched,name 等路由信息参数。而 $router 是“路由实例”对象包括了路由的跳转方法,钩子函数等。

vue 常用的修饰符?

.prevent: 提交事件不再重载页面;.stop: 阻止单击事件冒泡;.self: 当事件发生在该元素本身而不是子元素的时候会触发;

vue中 key 值的作用?

computed 和 watch 区别?

keep-alive 组件有什么作用?

如果你需要在组件切换的时候,保存一些组件的状态防止多次渲染,就可以使用 keep-alive 组件包裹需要保存的组件。

vue 中 mixin 和 mixins 区别?

mixin 用于全局混入,会影响到每个组件实例。

mixins 应该是我们最常使用的扩展组件的方式了。如果多个组件中有相同的业务逻辑,就可以将这些逻辑剥离出来,通过 mixins 混入代码,比如上拉下拉加载数据这种逻辑等等。另外需要注意的是 mixins 混入的钩子函数会先于组件内的钩子函数执行,并且在遇到同名选项的时候也会有选择性的进行合并,声明周期合并,方法覆盖

开发中常用的几种 Content-Type ?

(1)application/x-www-form-urlencoded

浏览器的原生 form 表单,如果不设置 enctype 属性,那么最终就会以 application/x-www-form-urlencoded 方式提交数据。该种方式提交的数据放在 body 里面,数据按照 key1=val1&key2=val2 的方式进行编码,key 和 val 都进行了 URL
转码。

(2)multipart/form-data

该种方式也是一个常见的 POST 提交方式,通常表单上传文件时使用该种方式。

(3)application/json

告诉服务器消息主体是序列化后的 JSON 字符串。

(4)text/xml

该种方式主要用来提交 XML 格式的数据。

如何判断一个对象是否为空对象?

function checkNullObj(obj) {
  return Object.keys(obj).length === 0;
}

使用闭包实现每隔一秒打印 1,2,3,4

// 使用闭包实现
for (var i = 0; i < 5; i++) {
  (function(i) {
    setTimeout(function() {
      console.log(i);
    }, i * 1000);
  })(i);
}

// 奇淫巧技利用setTimeout的第三个参数
for (var i = 0; i < 5; i++) {
    setTimeout(function() {
      console.log(i);
    }, i * 1000, i);
}

// 使用 let 块级作用域

for (let i = 0; i < 5; i++) {
  setTimeout(function() {
    console.log(i);
  }, i * 1000);
}

手写一个观察者模式?

var events = (function() {
  var topics = {};

  return {
    // 注册监听函数
    subscribe: function(topic, handler) {
      if (!topics.hasOwnProperty(topic)) {
        topics[topic] = [];
      }
      topics[topic].push(handler);
    },

    // 发布事件,触发观察者回调事件
    publish: function(topic, info) {
      if (topics.hasOwnProperty(topic)) {
        topics[topic].forEach(function(handler) {
          handler(info);
        });
      }
    },

    // 移除主题的一个观察者的回调事件
    remove: function(topic, handler) {
      if (!topics.hasOwnProperty(topic)) return;

      var handlerIndex = -1;
      topics[topic].forEach(function(item, index) {
        if (item === handler) {
          handlerIndex = index;
        }
      });

      if (handlerIndex >= 0) {
        topics[topic].splice(handlerIndex, 1);
      }
    },

    // 移除主题的所有观察者的回调事件
    removeAll: function(topic) {
      if (topics.hasOwnProperty(topic)) {
        topics[topic] = [];
      }
    }
  };
})();

EventEmitter 实现

class EventEmitter {
  constructor() {
    this.events = {};
  }

  on(event, callback) {
    let callbacks = this.events[event] || [];
    callbacks.push(callback);
    this.events[event] = callbacks;

    return this;
  }

  off(event, callback) {
    let callbacks = this.events[event];
    this.events[event] = callbacks && callbacks.filter(fn => fn !== callback);

    return this;
  }

  emit(event, ...args) {
    let callbacks = this.events[event];
    callbacks.forEach(fn => {
      fn(...args);
    });

    return this;
  }

  once(event, callback) {
    let wrapFun = function(...args) {
      callback(...args);

      this.off(event, wrapFun);
    };
    this.on(event, wrapFun);

    return this;
  }
}
Class Event {
    constructor() {
        this.events = {}
    }
    on(type, callback) {
        let callbacks = this.events[type] || []
        callbacks.push(callback)
        this.events[type] = callbacks
        return this
    }
    off(type, callback) {
        let callbacks = this.events[type]
        this.events[type] = callbacks.filter(fn => fn !== callback)
        return this
    }
    emit(type, args) {
        let callbacks = this.events[type]
        callbacks.forEach(callback => {
            callback(...args)
        })
        return this
    }
    once(type, callback) {
        let warpFn = function(...args) {
            callback(...args)
            this.off(event, warpFn)
        }
    }
    this.on(event, wrapFun);
    return this
}

一道常被人轻视的前端JS面试题

function Foo() {
  getName = function() {
    alert(1);
  };
  return this;
}
Foo.getName = function() {
  alert(2);
};
Foo.prototype.getName = function() {
  alert(3);
};
var getName = function() {
  alert(4);
};
function getName() {
  alert(5);
}

//请写出以下输出结果:
Foo.getName(); // 2
getName(); // 4
Foo().getName(); // 1
getName(); // 1
new Foo.getName(); // 2
new Foo().getName(); // 3
new new Foo().getName(); // 3

如何确定页面的可用性时间,什么是 Performance API?

Performance API 用于精确度量、控制、增强浏览器的性能表现。这个 API 为测量网站性能,提供以前没有办法做到的精度。

使用 getTime 来计算脚本耗时的缺点,首先,getTime方法(以及 Date 对象的其他方法)都只能精确到毫秒级别(一秒的千分之一),想要得到更小的时间差别就无能为力了。其次,这种写法只能获取代码运行过程中的时间进度,无法知道一些后台事件的时间进度,比如浏览器用了多少时间从服务器加载网页。

为了解决这两个不足之处,ECMAScript 5引入“高精度时间戳”这个 API,部署在 performance 对象上。它的精度可以达到1毫秒
的千分之一(1秒的百万分之一)。

navigationStart:当前浏览器窗口的前一个网页关闭,发生 unload 事件时的 Unix 毫秒时间戳。如果没有前一个网页,则等于 fetchStart 属性。

loadEventEnd:返回当前网页 load 事件的回调函数运行结束时的 Unix 毫秒时间戳。如果该事件还没有发生,返回 0。

var t = performance.timing;
var pageLoadTime = t.loadEventEnd - t.navigationStart;

Object.assign()

Object.assign() 方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。

js for 循环注意点

for (var i = 0, j = 0; i < 5, j < 9; i++, j++) {
  console.log(i, j);
}

// 当判断语句含有多个语句时,以最后一个判断语句的值为准,因此上面的代码会执行 10 次。
// 当判断语句为空时,循环会一直进行。

一个列表,假设有100000个数据,这个该怎么办?

我们需要思考的问题:该处理是否必须同步完成?数据是否必须按顺序完成?

解决办法:

(1)将数据分页,利用分页的原理,每次服务器端只返回一定数目的数据,浏览器每次只对一部分进行加载。

(2)使用懒加载的方法,每次加载一部分数据,其余数据当需要使用时再去加载。

(3)使用数组分块技术,基本思路是为要处理的项目创建一个队列,然后设置定时器每过一段时间取出一部分数据,然后再使用定时器取出下一个要处理的项目进行处理,接着再设置另一个定时器。

js 中倒计时的纠偏实现?

在前端实现中我们一般通过 setTimeout 和 setInterval 方法来实现一个倒计时效果。但是使用这些方法会存在时间偏差的问题,这是由于 js 的程序执行机制造成的,setTimeout 和 setInterval 的作用是隔一段时间将回调事件加入到事件队列中,因此事件并不是立即执行的,它会等到当前执行栈为空的时候再取出事件执行,因此事件等待执行的时间就是造成误差的原因。

一般解决倒计时中的误差的有这样两种办法:

(1)第一种是通过前端定时向服务器发送请求获取最新的时间差,以此来校准倒计时时间。

(2)第二种方法是前端根据偏差时间来自动调整间隔时间的方式来实现的。这一种方式首先是以 setTimeout 递归的方式来实现倒计时,然后通过一个变量来记录已经倒计时的秒数。每一次函数调用的时候,首先将变量加一,然后根据这个变量和每次的间隔时间,我们就可以计算出此时无偏差时应该显示的时间。然后将当前的真实时间与这个时间相减,这样我们就可以得到时间的偏差大小,因此我们在设置下一个定时器的间隔大小的时候,我们就从间隔时间中减去这个偏差大小,以此来实现由于程序执行所造成的时间误差的纠正。

如何查找一篇英文文章中出现频率最高的单词?

function findMostWord(article) {
  // 合法性判断
  if (!article) return;

  // 参数处理
  article = article.trim().toLowerCase();

  let wordList = article.match(/[a-z]+/g),
    visited = [],
    maxNum = 0,
    maxWord = "";

  article = " " + wordList.join("  ") + " ";

  // 遍历判断单词出现次数
  wordList.forEach(function(item) {
    if (visited.indexOf(item) < 0) {
      let word = new RegExp(" " + item + " ", "g"),
        num = article.match(word).length;

      if (num > maxNum) {
        maxNum = num;
        maxWord = item;
      }
    }
  });

  return maxWord + "  " + maxNum;
}

JSON.stringfy() JSON.parse() 有什么作用?有多少个参数?

JSON.stringify:有三个参数,第二个参数是对内容进行处理,第三个参数是设置缩进的值
str_pretty2 = JSON.stringify(str, null, 4)

JSON.parse():有两个参数,JSON.parse(text[, reviver])
JSON.parse('{"p": 5}', function (k, v) {
    if(k === '') return v;     // 如果到了最顶层,则直接返回属性值,
    return v * 2;              // 否则将属性值变为原来的 2 倍。
}); 

你知道的数组相关的API有哪些?

包括下一个答案的内容,还有pop、push、shift、unshift、slice、splice、reverse

除了 for of,遍历数组还有哪些方法?

1.for循环
2.forEach循环
3.map循环
4.for of循环
5.filter循环
6.every遍历
7.some遍历
8.reduce累加器
9.reduceRight从末位计算的累加器
10.find
11.findIndex
12.keys、values、entries
13.while

纯前端如何控制权限?你们项目控制权限用的是什么方式?

https://www.cnblogs.com/qixidi/p/10137973.html

下面数据如何结构赋值获取到line的第二个数据?

const node = {
    loc: {
      start: {
        line: [1,2,3],
        column: 5
      }
    }
};

let { loc: { start: {line:[,num]}} } = node;

webpack如何配置某个loader只遍历部分文件?

配置loader中的include设置遍历范围。

flex

space-around 两边对齐
space-between 边距相等

如何使多个元素相隔20px,但第一个元素保持不变?

div:not(:first-child){
    display:inline-block;
    margin-left:20px;
}

vue里面watch的作用,有什么其他参数吗?

观察 Vue 实例上的一个表达式或者一个函数计算结果的变化。
deep:true,为了发现对象内部值的变化
immediate:true ,将立即以表达式的当前值触发回调

computed的作用?computed和直接用一个函数进行属性计算的话,有什么区别?

计算属性是对属性值进行计算,会对原始值进行监听。 computed是基于它们的响应式依赖进行缓存的。只在相关响应式依赖发生改变时它们才会重新求值。 而一个函数进行计算显示的话每次刷新渲染都会重新执行函数。

JSON.stringify和JSON.parse实现深拷贝会有什么问题?

1、如果obj里面有时间对象,则JSON.stringify后再JSON.parse的结果,时间将只是字符串的形式。而不是时间对象;
2、如果obj里有RegExp、Error对象,则序列化的结果将只得到空对象;
3、如果obj里有函数,undefined,则序列化的结果会把函数或 undefined丢失;
4、如果obj里有NaN、Infinity和-Infinity,则序列化的结果会变成null;
5、JSON.stringify()只能序列化对象的可枚举的自有属性,例如:如果obj中的对象是有构造函数生成的,则使用JSON.parse(JSON.stringify(obj))深拷贝后,会丢弃对象的constructor

Promise内部的then是怎么实现的?如何实现.then的链式调用?

Promise 实例具有then方法,也就是说,then方法是定义在原型对象Promise.prototype上的。then方法返回的是一个新的Promise实例。因此可以采用链式写法,即then方法后面再调用另一个then方法。

块级作用域是指哪些地方?

for循环
if判断的大括号
函数内部

箭头函数的一个用处是简化回调函数。

箭头函数的一个用处是简化回调函数。

解释一下原型链是什么意思?那es6里面的class和原型链有什么关系?

访问一个对象的属性时,先在基本属性中查找,如果没有,再沿着__proto__这条链向上找,这就是原型链。参考链接。
Class 可以通过extends关键字实现继承,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多。

min-height和!important同时设置的话生效的会是什么呢?

min-height会先生效,覆盖掉!important

重绘和回流是什么?什么会引起重绘?什么会引起回流?

当render tree中的一部分(或全部)因为元素的规模尺寸,布局,隐藏等改变而需要重新构建。这就称为回流
不会影响布局的,比如background-color。则就叫称为重绘。

回流必将引起重绘,而重绘不一定会引起回流。

let和var声明变量的区别?var声明的变量会到window可以读取,那let可以吗?为什么

var 存在提升,我们能在声明之前使用。let、const 因为暂时性死区的原因,不能在声明前使用let 在全局作用域下声明变量会导致变量挂载在 window 上,其他两者不会

let的不会挂载到window上,let命令所在的代码块内有效。

const和let的区别是什么?const声明的对象设置之后能改里面的内容吗?

const声明一个只读的常量。一旦声明,常量的值就不能改变。const的作用域与let命令相同:只在声明所在的块级作用域内有效。声明的常量也是不提升,同样存在暂时性死区,只能在声明的位置后面使用。

引用类型和基本类型的储存区别是什么?

原始类型的数据值都是直接保存在“栈”中的,引用类型的值是存放在“堆”中的

解释一下闭包是什么意思?

根据词法作用域的规则,内部函数总是可以访问其外部函数中声明的变量,当通过调用一个外部函数返回一个内部函数后,即使该外部函数已经执行结束了,但是内部函数引用外部函数的变量依然保存在内存中,我们就把这些变量的集合称为闭包。

for of循环可以循环对象吗?for in可以循环一个数组吗?

for of循环不可以循环对象。一个数据结构只要部署了Symbol.iterator属性,就被视为具有 iterator 接口,就可以用for...of循环遍历它的成员。对象没有Symbol.iterator。
for in循环可以循环数组吗。可以,只能取到key。for in取到的都是key值。

apply和call的作用和差别?

两者都是在fun函数运行时指定的this值,不同的是
fun.call(thisArg, arg1, arg2, ...)
fun.apply(thisArg, [argsArray])
apply将函数的参数放在第二个参数用数组传入,call是逐个参数传入。

map和set的使用场景有哪些?

Map适合储存键值对的数据 Set不保存重复的元素,检索元素效率低下,删除和插入效率高.

WeakSet和WeakMap

WeakSet 中的对象都是弱引用,即垃圾回收机制不考虑 WeakSet 对该对象的引用.
WeakMap 与 Map 在 API 上的区别主要是两个:
一是没有遍历操作(即没有keys()、values()和entries()方法,也没有size属性。
二是无法清空,即不支持clear方法。

es6装饰器是什么概念?迭代器又是什么概念?

装饰器并未实装。装饰器(Decorator)是一种与类(class)相关的语法,用来注释或修改类和类方法。
遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。

手写reduce方法

Array.prototype.myReduce = function(cb, init) {
    let acc = init ? init : this[0]
    for (let index = init ? 0 : 1; index < this.length; index++) {
        acc = cb(acc, this[index])
    }
    return acc
}

instanceof

function instanceof(left, right) {
    let prototype = right.prototype
    let left = left.__proto__
    while(true) {
        if (left === null) return false
        if (left === prototype) return true
        left = left.__proto__
    }
}

如何判断一个对象是否为数组

function isArray(arr) {
    return Object.prototype.toString.call(arr) === '[object Array]'
}

冒泡排序

var arr = [3, 1, 4, 6, 5, 7, 2d];
function bubbleSort(arr) {
    for (let i = 0; i < arr.length - i; i++) {
        for (let j = 0; j < arr.length - i - 1; j++) {
            if (arr[j+1] > arr[j]) {
                let temp
                temp = arr[j]
                arr[j] = arr[j+1]
                arr[j+1] = temp
            }
        }
    }
}

快速排序

采用二分法,取出中间数,数组每次和中间数比较,小的放到左边,大的放到右边

var arr = [3, 1, 4, 6, 5, 7, 2];
function quickSort(arr) {
    if (arr.length === 0) {
        return []
    }
    let midIndex = Math.floor(arr.length/2)
    let mid = arr.splice(midIndex, 1)[0]
    let left = []
    let right = []
    for (let i = 0; i < arr.length; i++) {
        if(arr[i] < mid) {
            l.push(arr[i]);
        } else {
            r.push(arr[i]);
        }
    }
    return quickSort(left).concat([mid], quickSort(right));
}

编写一个方法 求一个字符串的字节长度

function getBytes(str) {
    var len = str.length
    var bytes = len;
    for(var i=0; i<len; i++){
        if (str.charCodeAt(i) > 255) bytes++;
    }
    return bytes;
}

下面这个ul,如何点击每一列的时候alert其index

 <ul id=”test”>
     <li>这是第一条</li>
     <li>这是第二条</li>
     <li>这是第三条</li>
 </ul>
document.getElementById("box").addEventListener("click",function(e) {
    if(e.target && e.target.nodeName == "LI") {
        var children = this.children;    //获取ul里面的所有li元素集合
        for(var i=0;i<children.length;i++){
            if(e.target == children[i]) { //对比目标元素和li集合元素
                alert("目标元素的下标为:" + i);    //输出目标元素的下标
            }
        }
    }
});


var li = $("box").getElementsByTagName("li");
for(var j=0;j<list2.length;j++){
	li[j].onclick = (function(num){
		return function(){
			alert(num);
		};
	})(j);
}

定义一个log方法,让它可以代理console.log的方法

function log() {
    console.log.apply(console, arguments)
}

输出今天的日期

var d = new Date();
// 获取年,getFullYear()返回4位的数字
var year = d.getFullYear();
// 获取月,月份比较特殊,0是1月,11是12月
var month = d.getMonth() + 1;
// 变成两位
month = month < 10 ? '0' + month : month;
// 获取日
var day = d.getDate();
day = day < 10 ? '0' + day : day;
alert(year + '-' + month + '-' + day);

node有哪些特征

单线程、事件驱动、非阻塞I/O