面闭 — JavaScript

Ajax 请求的步骤

第一步(得到 XMLHttpRequest)

第二步(打开与服务器的链接)

第三步(发送请求)

xmlHttp.send(null);如果括号里不给 null 可能会造成部分浏览器无法发送;

第四步

在 xmlHttp 对象的一个事件上注册监听器: onreadstatechange

得到的 5 个状态

>0状态:刚创建,还没有调用 open() 方法;

>1状态:请求刚开始,调用了open()方法,但是没有调用 send 方法;

>2状态:调用完了 send() 方法;

>3状态:服务器已经开始响应。但不表示响应结束;

>4状态:服务器响应结束!;

得到 xmlHttp 对象状态

var state = xmlHttp.redayState; // 可能是 0,1,2,3,4

得到服务器的响应状态码

var state = xmlHttp.status; // 可能是 200、404、500

得到服务器的响应内容

Ajax 的缺点

(1)、ajax 不支持浏览器 back 按钮

(2)、安全问题 ajax 暴露了与服务器交互的细节。

(3)、对搜索引擎的支持比较弱

(4)、破坏了程序的异常机制

什么是 Ajax 和 JSON,它们的优缺点

Ajax 全称 asynchronous JavaScript and XML,即异步 JavaScript 和 xml,用于在 web 页面中事先异步数据交互,实现页面局部刷新。

优点:可以使得页面不重载全部内容的情况下加载局部内容,降低数据传输量,避免用户不断刷新或者跳转页面,提高用户体验。

缺点:对搜索引擎不友好,要实现 ajax 下的前后退功能成本较大,可能造成请求数量增加跨域问题限制;

json 是一种轻量级的数据交换格式,ECMA 的一个子级

优点:轻量级、易于人的阅读和编写,便于机器(JavaScript)解析,支持复合数据类型(数组、对象、字符串、数字)

Ajax 请求的时候 get 和 post 方式的区别

get 一般用来进行查询操作,url 地址有长度限制,请求的参数都暴露在 url 地址中,如果传递中文参数,需要自己进行编码操作,安全性低

post 请求方式主要提交数据,没有数据长度的限制,提交的数据内容存在于 http 请求体中,数据不会暴露在 url 地址中。

jsonp的原理,以及为什么不是真正的 ajax

jsonp 并不是一种数据格式,而 json 是一种数据格式,jsonp 是用来解决跨域获取数据的一种解决方案,具体是通过动态创建 script 标签,然后通过标签的 src 属性获取 js 文件中的 js 脚本,该脚本的内容是一个函数调用,参数就是服务器返回的数据,为了处理这些返回的数据,需要事先在页面定义好回调函数,本质上不是使用 ajax 技术。

阐述一下异步加载

(1) 异步加载的方案:动态插入 script 标签

(2) 通过 ajax 去获取 js 代码,然后通过 eval 执行

(3) script 标签上添加 defer 或 async 属性

(4) 创建并插入 iframe,让它异步执行 js

post 和 get 的区别,何时使用 post

get:一般用于信息获取,使用 url 传递参数,对所有发送信息的数量也有限制,一般在 2000 个字符,有的浏览器 8000 个字符

post:一般用于修改服务器上的资源,对于发送的信息没有限制

在以下情况中,请使用 post 请求:

(1) 无法使用缓存文件(更新服务器上的文件或数据库)

(2) 向服务器发送大量数据(post 没有数据限制)

(3) 发送包含未知字符的用户输入时,post 比 get 更稳定也更可靠

数组去重的方法

排序方法

1
2
3
4
5
6
7
8
9
10
function distinct(arr){
var result = []
for(var i = 0;i < arr.length; i++){
for(var j = i+1;j<arr.length;j++){
j = ++i;
}
result.push(arr[i])
}
return result;
}

对象方法

1
2
3
4
5
6
7
8
9
10
11
function unique(arr){
var res = []
var json = {}
for(var i=0;i<arr.length;i++){
if(!json[arr[i]]){
res.push(arr[i])
json[arr[i]] = 1;
}
}
return res;
}

js 的作用域

作用域说明:一般理解指一个变量的作用范围

1、全局作用域

(1)全局作用域在页面打开时被创建,页面关闭时被销毁

(2)编写在 script 标签中的变量和函数,作用域为全局,在页面的任意位置都可以访问到

(3)在全局作用域中有全局对象 window,代表一个浏览器窗口,由浏览器创建,可以直接调用

(4)全局作用域中声明的变量和函数会作为 window 对象的属性和方法保存

2、函数作用域

(1)调用函数时,函数作用域被创建,函数执行完毕,函数作用域被销毁

(2)每调用一次函数就会被创建一个新的函数作用域,他们之间是相互独立的。

(3)在函数作用域中可以访问到全局作用域的变量,在函数外无法访问到函数作用域内的变量

(4)在函数作用域中访问变量、函数时,会先在自身作用域中寻找,若没有找到,则会到函数的上一级作用域中寻找,一直到全局作用域

说说你对闭包的理解

说说你对闭包的理解

使用闭包主要是为了设计私有的方法和变量。

闭包的优点是可以避免全局变量的污染,

缺点是闭包会常驻内存,会增大内存使用量,使用不当很容易造成内存泄露。

闭包有三个特性:

1、函数嵌套函数

2、函数内部可以引用外部的参数和比变量

3、参数和变量不会被垃圾回收机制回收

cookie 虽然在持久保存客户数据提供了方便,分担了服务器存储的负担,但是有很多局限性。

1、每个域名下最多生成 20 个 cookie.

2、浏览器会清理 cookie ,IE、Opera 会清理近期最少使用的 cookie,Firefox 会随机清理 cookie.

3、cookie 最大大约为 4096 字节。

优点:极高的扩展性和可用性。

1、通过良好的编程,控制保存在 cookie 中的 session 对象的大小。

2、通过加密和安全传输技术(SSL),减少 cookie 被破解的可能性。

3、只在 cookie 中存放不敏感数据,即使被盗也不会有重大损失。

4、控制 cookie 的生命期,使之不会永远有效。盗窃者很可能拿到一个过期的 cookie。

缺点:

1、cookie 数量和长度的限制。

2、安全性问题。

3、有些状态不可能保存在客户端。

1、cookie 数据存放在客户的浏览器上,session 数据放在服务器上。

2、cookie 不是很安全,可以分析存放在本地的 cookie 并进行 cookie 欺骗,考虑到安全应当使用 session。

3、session 会在一定时间内保存在服务器上。当访问增多,会比较占用服务器的性能。

4、单个 cookie 保存的数据不能超过 4k,一个站点最多保存20个 cookie。

使用 typeof bar === “object” 来确定 bar 是否是对象的潜在陷阱是什么?如何避免这个陷阱?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 避免 null 返回 true
var bar = null;
console.log((bar !== null) && (typeof bar === "object")); // logs false

// function 返回 ture
var bar = function(){
};
console.log((bar !== null) && ((typeof bar === "object") || (typeof bar === "function")));

// 数组 返回 false
var bar = [];
console.log((bar !== null) && (typeof bar === "object") && (toString.call(bar) !== "[object Array]"));

// jQ
console.log((bar !== null) && (typeof bar === "object") && (! $.isArray(bar)));
下面的代码将输出什么到控制台,为什么
1
2
3
4
5
6
7
8
9
10
(function(){
var a = b = 3; // b = 3;var a = b;
})();

console.log("a defined? " + (typeof a !== 'undefined'));
console.log("b defined? " + (typeof b !== 'undefined'));

// output
a defined? false
b defined? true
下面输出什么到控制台,为什么
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var myObject = {
foo: "bar",
func: function() {
var self = this;
console.log("outer func: this.foo = " + this.foo);
console.log("outer func: self.foo = " + self.foo);
(function() {
console.log("inner func: this.foo = " + this.foo);
console.log("inner func: self.foo = " + self.foo);
}());
}
};

myObject.func();
// 在ECMA 5之前,在内部函数中的this 将指向全局的 window 对象;
/*
* outer func: this.foo = bar
* outer func: self.foo = bar
* inner func: this.foo = undefined
* inner func: self.foo = bar
*/
封装JavaScript源文件的全部内容到一个函数块有什么意义及理由

这是一个越来越普遍的做法,被许多流行的JavaScript库(jQuery,Node.js等)采用。这种技术创建了一个围绕文件全部内容的闭包,也许是最重要的是,创建了一个私有的命名空间,从而有助于避免不同JavaScript模块和库之间潜在的名称冲突。

这种技术的另一个特点是,允许一个易于引用的(假设更短的)别名用于全局变量。这通常用于,例如,jQuery插件中。jQuery允许你使用jQuery.noConflict(),来禁用 $ 引用到jQuery命名空间。在完成这项工作之后,你的代码仍然可以使用$ 利用这种闭包技术,如下所示:

1
(function($) { /* jQuery plugin code referencing $ */ } )(jQuery);

在 JavaScript 源文件的开头包含 use strict 意义和好处

use strict 是一种在JavaScript代码运行时自动实行更严格解析和错误处理的方法。

严格模式的一些主要优点包括:

  • 使调试更加容易。
  • 防止意外的全局变量。
  • 消除 this 强制
  • 不允许重复的属性名称或参数值。
  • 使eval() 更安全。
  • 在 delete使用无效时抛出错误。
考虑以下两个函数。它们会返回相同的东西吗? 为什么相同或为什么不相同
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function foo1()
{
return {
bar: "hello"
};
}

function foo2()
{
return
{
bar: "hello"
};
}
console.log(foo1());
console.log(foo2());

// output
// Object {bar: "hello"};
// undefined

// 代码行上没有其他任何代码,分号会立即自动插入到返回语句之后。
NaN 是什么?它的类型是什么?你如何可靠地测试一个值是否等于 NaN

NaN 属性代表一个“不是数字”的值。这个特殊的值是因为运算不能执行而导致的,不能执行的原因要么是因为其中的运算对象之一非数字(例如, “abc” / 4),要么是因为运算的结果非数字(例如,除数为零)。

NaN 是 Number;

检测
isNaN
Number.isNaN()

下列代码将输出什么?并解释原因

console.log(0.1 + 0.2);
console.log(0.1 + 0.2 == 0.3);

JavaScript中的数字和浮点精度的处理相同,因此,可能不会总是产生预期的结果。

9.讨论写函数 isInteger(x) 的可能方法,用于确定x是否是整数

在ECMAScript规格说明中,整数只概念上存在:即,数字值总是存储为浮点值。

function isInteger(x) { return (x^0) === x; }
function isInteger(x) { return Math.round(x) === x; }
function isInteger(x) { return (typeof x === ‘number’) && (x % 1 === 0);
function isInteger(x) { return parseInt(x, 10) === x; } // 目变得足够大,为指数形式(例如, 1e+21)

Math.ceil() 和 Math.floor() 在上面的实现中等同于 Math.round()。

下列代码行1-4如何排序,使之能够在执行代码时输出到控制台? 为什么

(function() {
console.log(1);
setTimeout(function(){console.log(2)}, 1000);
setTimeout(function(){console.log(3)}, 0);
console.log(4);
})();

output
1
4
3
2

浏览器有一个事件循环,会检查事件队列和处理未完成的事件。例如,如果时间发生在后台(例如,脚本的 onload 事件)时,浏览器正忙(例如,处理一个 onclick),那么事件会添加到队列中。当onclick处理程序完成后,检查队列,然后处理该事件(例如,执行 onload 脚本)。
同样的, setTimeout() 也会把其引用的函数的执行放到事件队列中,如果浏览器正忙的话。
当setTimeout()的第二个参数为0的时候,它的意思是“尽快”执行指定的函数。具体而言,函数的执行会放置在事件队列的下一个计时器开始。但是请注意,这不是立即执行:函数不会被执行除非下一个计时器开始。这就是为什么在上述的例子中,调用 console.log(4) 发生在调用 console.log(3) 之前(因为调用 console.log(3) 是通过setTimeout被调用的,因此会稍微延迟)。

写一个简单的函数(少于80个字符),要求返回一个布尔值指明字符串是否为回文结构

function isPalindrome(str) {
str = str.replace(/W/g, ‘’).toLowerCase();
return (str == str.split(‘’).reverse().join(‘’));
}

console.log(isPalindrome(“level”)); // logs ‘true’
console.log(isPalindrome(“levels”)); // logs ‘false’
console.log(isPalindrome(“A car, a man, a maraca”)); // logs ‘true’

写一个 sum方法,在使用下面任一语法调用时,都可以正常工作

console.log(sum(2,3)); // Outputs 5
console.log(sum(2)(3)); // Outputs 5

柯里化

1
2
3
4
5
6
7
function sum(x) {
if (arguments.length == 2) {
return arguments[0] + arguments[1];
} else {
return function(y) { return x + y; };
}
}
1
2
3
4
5
6
7
function sum(x, y) {
if (y !== undefined) {
return x + y;
} else {
return function(y) { return x + y; };
}
}

下面console的输出结果是。

1
2
3
4
5
6
7
8
var name = “one”;
var User =
function ( ) {
this.name = “two”;
}
var obj =
User( );
console.log(name);

答案:two

阅读如下代码,请问两处console的输出结果。

1
2
3
4
5
6
7
8
9
10
var Product = {
count: 1,
getCount: function( ) {
return this.count++;
}
};

console.log(Product.getCount( ));
var func = Product.getCount;
console.log(func( ));

答案: 1 NaN

Array.prototype.slice.call(arr,2)方法的作用是:

答案:以arr为基础,并调用其slice方法,截取从索引为2到末尾位置

http请求中GET和POST方法的区别是()。

答案:
get是从服务器上获取数据,post是向服务器传送数据。
get是把参数数据队列加到提交表单的ACTION属性所指的URL中,值和表单内各个字段一一对应,在URL中可以看到。post是通过HTTPpost机制,将表单内各个字段与其内容放置在HTML HEADER内一起传送到ACTION属性所指的URL地址。用户看不到这个过程。
对于get方式,服务器端用Request.QueryString获取变量的值,对于post方式,服务器端用Request.Form获取提交的数据。
get传送的数据量较小,不能大于2KB。post传送的数据量较大,一般被默认为不受限制。但理论上,IIS4中最大量为80KB,IIS5中为100KB。(这里有看到其他文章介绍get和post的传送数据大小跟各个浏览器、操作系统以及服务器的限制有关)
get安全性非常低,post安全性较高。

简述浏览器发起一个网络请求(HTTP请求事务)后,都经历了哪些步骤:

输入网址:输入url地址
域名解析:浏览器按照顺序解析,自身的dns缓存——客户端自身的dns缓存——本地host文件——路由器缓存
建立连接:浏览器获得域名对应的ip地址后,发起tcp三次握手,将客户端与服务端建立连接(http基于tcp协议,tcp为传输层协议)
返回数据:服务端接收请求并将数据返回给浏览器
处理数据:浏览器拿到返回资源后进行客户端渲染,将完整页面呈现给用户。

请为所有数组对象添加一个通用的 remove 方法,参数是数组元素的索引值,实现删除指定数组元素的索引的功能。(可以写伪代码)。例如:var arr=[1,2,3,4,5,6]; arr.remove(3); 修改后的arr为[1,2,3,5,6]。
1
2
3
4
5
6
7
8
9
10
// 答案:思想就是找到要删除的元素,后面的元素依次前移
Array.prototype.remove= function(i){
if(isNaN(i) || i < 0 || i >=this.length){
return this;
}
for(var j=i; j<this.length-1; j++){
this[j] = this[j+1];
}
this.length-=1;
};

手写

实现一个 new 操作符

new 操作符做了这些事:

  • 它创建了一个全新的对象
  • 它会被执行 [[Prototype]] (也就是 __proto__)链接
  • 它使 this 指定新创建的对象
  • 通过 new 创建的每个对象将最终被 [[Prototype]] 链接到这个函数的 prototype 对象上
  • 如果函数没有返回对象类型 Object (包含 Function,Array,Date,RegExg,Error),那么 new 表达式中的函数调用将返回该对象引用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function New(func) {
var res = {};
if(func.prototype !== null){
res.__proto__ = func.prototype;
}
var ret = func.apply(res, Array.prototype.slice.call(arguments,1));
if((typeof ret === 'object' || typeof ret === 'function') && ret !== null){
return ret;
}
return res;
}

var obj = New(A,1,2);
// equals to
var obj = new A(1,2);
实现一个 JSON.stringify

JSON.stringify(value [,replacer[,space]])

  • Boolean | Number | String 类型会自动转换成对应的原始值
  • undefined、任意函数以及 symbol,会被忽略(出现在非数组对象的属性值中时),或者被转换成 null (出现在数组中时)。
  • 不可枚举的属性会被忽略
  • 如果一个对象的属性值通过某种间接的方式指回该对象本身,即循环引用,属性也会被忽略。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
function jsonStringify(obj){
let type = typeof obj;
if(type !== 'object'){
if(/string|undefined|function/.test(type)){
obj = '""' + obj + '""'
}
return String(obj);
}else{
let json = [];
let arr = Array.isArray(obj);
for(let k in obj){
let v = obj[k];
let type = typeof v;
if(/string|undefined|function/.test(type)){
v = '""' + v + '""';
}else if(type === "object"){
v = jsonStringify(v);
}
json.push((arr ? "" : '"' + k + '":') + String(v));
return (arr ? "[":"{") + String(json) + (arr ? "]" : "}")
}
}
}

jsonStringify({ x : 5}) // "{"x":5}"
jsonStringify([ 1, "false" , false]) // "[1,"false",false]"
jsonStringify({ b : undefined}) // "{"b":"undefined"}"
实现一个 JSON.parse

JSON.parse(text[,reviver])

eval
1
2
3
4
5
6
7
8
9
10
11
12
13
14
function jsonParse(json){
var rx_one = /^[\],:{}\s]*$/;
var rx_two = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/;
var rx_three = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g;
var rx_four = /(?:^|:|,)(?:\s*\[)+/g;
if(
rx_one.test(json.replace(rx_two,"@").replace(rx_three,"]").replace(rx_four,""))
){
return eval("("+json+")");
}
}
jsonParse(JSON.stringify({ x : 5})); // Object { x: 5}
jsonParse(JSON.stringify([ 1,'false',false])) // [1, "false" ,false]
jsonParse(JSON.stringify({ b : undefined})) // Object { b : "undefined"}
Function
1
2
var jsonStr = '{ "age": "20", "name": "jack"}'
var json = (new Function('return' + jsonStr ))();
实现一个 call 或 apply
Function.call

call 核心:

  • 将函数设为对象的属性
  • 执行&删除这个函数
  • 指定 this 到函数并传入给定参数执行函数
  • 如果不传入参数,默认指向为 window
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//  简单版
var foo = {
value: 1,
bar: function(){
console.log(this.value)
}
}
foo.bar() //1

// 完善版
Function.prototype.call2 = function(content = window){
content.fn = this;
let args = [...arguments].slice(1);
let result = content.fn(...args);
delete content.fn;
return result;
}

let foo = {
value: 1;
}

function bar(name , age){
console.log(name);
console.log(age);
console.log(this.value);
}
bar.call2(foo,'black' , '18'); // back 18 1

Function.apply 的模拟实现

1
2
3
4
5
6
7
8
9
10
11
12
Function.prototype.apply2 = function (context = window){
context.fn = this
let result;
// 判断是否有第二个参数
if(arguments[1]){
result = context.fn(...arguments[1])
}else{
result = context.fn()
}
delete content.fn;
return result;
}
实现一个 Function.bind()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Function.prototype.bind2 = function( content ){
if(typeof this != 'function'){
throw Error("not a function")
}

let fn = this;
let args = [...arguments].slice(1);
let resFn = function(){
return fn.apply(this instanceof resFn ? this : content,args.concat(...arguments))
}

function tmp() {}
tmp.prototype = this.prototype;
resFn.prototype = new tmp();

return resFn;
}
实现一个继承

核心实现是:用一个 F 空的构造函数取代执行了 Parent 这个构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
function Parent(name) {
this.name = name;
}

Parent.prototype.sayName = function(){
console.log('parent name', this.name);
}

function Child(name,parentName){
Parent.call(this,parentName);
this.name = name;
}

function create(proto) {
function F(){}
F.prototype = proto;
return new F();
}

Child.prototype = create(Parent.prototype);
Child.prototype = sayName = function(){
console.log('child name' , this.name);
}

Child.prototype.constructor = Child;

var parent = new Parent('father');
parent.sayName();
var child = new Child('son','father');
实现一个JS函数柯里化

函数柯里化的主要作用和特点就是参数复用、提前返回和延迟执行。

通用版
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function curry(fn , args) {
var length = fn.length;
var args = args || [];
return function(){
newArgs = args.concat(Array.prototype.slice.call(arguments));
if(newArgs.length < length){
return curry.call(this,fn,newArgs);
}else{
return fn.apply(this,newArgs);
}
}
}

function multiFn(a, b, c){
return a * b * c;
}

var multi = curry(multiFn);

multi(2)(3)(4);
multi(2,3,4);
multi(2)(3,4);
multi(2,3)(4);
ES6
1
2
3
4
5
6
7
8
const curry = (fn,arr = []) => (...args) => (
arg => arg.length === fn.length ? fn(...arg) : curry(fn,arg)
)([...arr,...args])

let curryTest = curry((a,b,c,d) => a + b + c + d)
curryTest(1,2,3)(4)
curryTest(1,2)(4)(3)
curryTest(1,2)(3,4)
手写一个Promise(中高级必考)

Promise/A+ 规范:

  • 三种状态 pending|fulfilled(resolved)|rejected
  • 当处于 pending 状态的时候,可以转移到 fulfilled(resolved) 或者 rejected 状态
  • 当处于 fulfilled( resolved ) 状态或者 rejected 状态的时候,就不可变
  • 必须有一个 then 异步执行方法, then 接受两个参数且必须返回一个 promise
面试够用版
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
function myPromise(constructor){
let self = this;
self.status = "pending" // 定义状态改变前的初始状态
self.value = undefined; // 定义状态为 resolved 的时候的状态
self.reason = undefined; // 定义状态为 rejected 的时候的状态

function resolve(value){
// 两个 === pending 保证了状态的改变是不可逆的
if(self.status === 'pending'){
self.value = value;
self.status = 'resolved';
}
}

function reject(reason){
// 两个 === pending,保证了状态的改变是不可逆的
if(self.status === 'pending'){
self.reason = reason;
self.status = "rejected";
}
}

// 捕获构造异步
try{
constructor(resolve,reject);
}catch(e){
reject(e)
}
}

myPromise.prototype.then = function(onFullfilled,onRejected){
let self = this;
switch(self.status){
case "resolved":
onFullfilled(self.value);
break;
case "rejected":
onRejected(self.reason)
break;
default:
}
}
大厂专供版
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

function Promise(excutor) {
let that = this; // 缓存当前promise实例对象
that.status = PENDING; // 初始状态
that.value = undefined; // fulfilled状态时 返回的信息
that.reason = undefined; // rejected 状态时 拒绝的原因
that.onFulfilledCallbacks = []; // 存储 fulfilled 状态对应的 onFulfilled 函数
that.onRejectedCallbacks = []; // 存储 rejected 状态对应的 onRejected 函数

function resolve(value){ // value 成功态时接收的终值
if(value instanceof Promise){
return value.then(resolve,reject)
}
// 确保 onFulfilled 和 onRejected 方法异步执行,且应该在 then 方法被调用事件轮询循环之后新执行栈中。
setTimeout(()=>{
// 调用 resolve 回调对应 onFulfilled 函数
if(that.status === PENDING){
// 只能由 pendding 状态 => fuifilled 状态(避免调用多次 resolve reject)
that.status = FULFILLED;
that.value = value;
that.onFulfilledCallbacks.forEach(cb = > cb(that.value));
}
});
}

function reject(reason) { // reason失败态时接收的拒因
setTimeout(() => {
// 调用 reject 回调对应 onRejected 函数
if(that.status === PENDING ){
that.status = REJECTED
that.reason = reason;
that.onRejectedCallbacks.forEach(cb = > cb(that.reason));
}
});
}

// 捕获在excutor执行器中抛出的异常
try{
excutor(resolve,reject);
}catch(e){
reject(e);
}
}

Promise.prototype.then = function(onFulfilled,onRejected){
const that = this;
let newPromise;
// 处理参数默认值 保证参数后续能够继续执行
onFulfilled = typeof onFulfilled === "function" ? onFulfilled : value => value;
onRejected = typeof onRejected === "function" ? onRejected : reason => { throw reason;}
if(that.status === FULFILLED){ // 成功态
return newPromise = new Promise((resolve,reject) => {
setTimeout(()=>{
try{
let x = onFulfilled(that.value);
resolvePromise(newPromise,x,resolve,reject)
}catch(e){
reject(e);
}
})
})
}
}

if(that.status === REJECTED){
return newPromise = new Promise((resolve,reject) =>{
setTimeout(() => {
try{
let x = onFulfilled(that.value);
resolvePromise(newPromise,x,resolve,reject)
}catch{
reject(e);
}
})
})
}

if(that.status === PENDING){ // 等待态
return newPromise = new Promise((resolve,reject) => {
that.onFulfilledCallback.push((value) => {
try{
let x = onFulfilled(that.value);
resolvePromise(newPromise,x,resolve,reject)
}catch{
reject(e);
}
})
that.onRejectedCallbacks.push((reason) => {
try{
let x = onFulfilled(that.value);
resolvePromise(newPromise,x,resolve,reject)
}catch{
reject(e);
}
})
})
}
手写防抖(Debouncing)和节流(Throttling)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// 防抖动函数
function debounce(fn,wait=50,immediate){
let timer;
return function(){
if(immediate){
fn.apply(this,arguments);
}
if(timer) clearTimeout(timer);
timer = setTimeout(()=>{
fn.apply(this,arguments)
},wait)
}
}

// 简单的节流函数:
function throttle(fn,wait){
let prev = new Date();
return function(){
const args = arguments;
const now = new Date();
if(now - pre > wait){
fn.apply(this,args);
prev = new Date();
}
}
}

// 通过第三个参数切换模式

const throttle = function(fn, delay, isDebounce){
let timer
let lastCall = 0
return function (...args){
if( isDebounce ){
if( timer ) clearTimeout(timer)
timer = setTimeout (() =>{
fn (...args)
}, delay)
}else{
const now = new Date().getTime()
if(now - lastCall < delay) return
lastCall = now
fn(...args)
}
}
}
手写一个JS深拷贝
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 极简版
var newObj = JSON.parse( JSON.stringify(someObj));

// 面试够用版

function deepCopy(obj){
// 判断是否是简单数据类型
if(typeof obj == "object"){
var result = obj.constructor == Array ? [] : {};
for ( let i in obj ){
result [i] = typeof obj [i] == "object" ? deepCopy (obj [i]) :obj [i];}
}else{
// 简单数据类型 直接 == 赋值
var result = obj;
}
return result;
}
实现一个instanceOf
1
2
3
4
5
6
7
8
9
function 实现一个instanceOf(left,right){
let proto = left.__proto__;
let prototype = right.prototype
while(true){
if(proto === null) return false
if(proto === prototype) return true
proto = proto.__proto__;
}
}

相关资料

高频前端开发面试问题

「中高级前端面试」JavaScript手写代码无敌秘籍
25 个最基本的 JavaScript 面试问题及答案(上)

前端面试高频手写代码题
25 个最基本的 JavaScript 面试问题及答案(下)
58道Vue常见面试题集锦,涵盖入门到精通,自测 Vue 掌握程度
由浅入深,66条JavaScript面试知识点和答案解析
2 年前端面试心路历程(字节跳动、YY、虎牙、BIGO)
蚂蚁、字节、滴滴面试经历总结(都已过)
45道JS能力测评经典题总结

07—47道基础的VueJS面试题(附答案)

一文帮你搞定 90% 的 JS 手写题!面试手写题不慌了 (qq.com)

作者

Fallen-down

发布于

2020-07-30

更新于

2021-08-06

许可协议

You need to set install_url to use ShareThis. Please set it in _config.yml.
You forgot to set the business or currency_code for Paypal. Please set it in _config.yml.

评论

You forgot to set the shortname for Disqus. Please set it in _config.yml.
You need to set client_id and slot_id to show this AD unit. Please set it in _config.yml.