乐趣区

关于前端:2023我的前端面试小结

代码输入后果

function Person(name) {this.name = name}
var p2 = new Person('king');
console.log(p2.__proto__) //Person.prototype
console.log(p2.__proto__.__proto__) //Object.prototype
console.log(p2.__proto__.__proto__.__proto__) // null
console.log(p2.__proto__.__proto__.__proto__.__proto__)//null 前面没有了,报错
console.log(p2.__proto__.__proto__.__proto__.__proto__.__proto__)//null 前面没有了,报错
console.log(p2.constructor)//Person
console.log(p2.prototype)//undefined p2 是实例,没有 prototype 属性
console.log(Person.constructor)//Function 一个空函数
console.log(Person.prototype)// 打印出 Person.prototype 这个对象里所有的办法和属性
console.log(Person.prototype.constructor)//Person
console.log(Person.prototype.__proto__)// Object.prototype
console.log(Person.__proto__) //Function.prototype
console.log(Function.prototype.__proto__)//Object.prototype
console.log(Function.__proto__)//Function.prototype
console.log(Object.__proto__)//Function.prototype
console.log(Object.prototype.__proto__)//null

这道义题目考查原型、原型链的根底,记住就能够了。

说一下 SPA 单页面有什么优缺点?

长处:1. 体验好,不刷新,缩小 申请  数据 ajax 异步获取 页面流程;2. 前后端拆散

3. 加重服务端压力

4. 共用一套后端程序代码,适配多端

毛病:1. 首屏加载过慢;2.SEO 不利于搜索引擎抓取

为什么 0.1+0.2 ! == 0.3,如何让其相等

在开发过程中遇到相似这样的问题:

let n1 = 0.1, n2 = 0.2
console.log(n1 + n2)  // 0.30000000000000004

这里失去的不是想要的后果,要想等于 0.3,就要把它进行转化:

(n1 + n2).toFixed(2) // 留神,toFixed 为四舍五入

toFixed(num) 办法可把 Number 四舍五入为指定小数位数的数字。那为什么会呈现这样的后果呢?

计算机是通过二进制的形式存储数据的,所以计算机计算 0.1+0.2 的时候,实际上是计算的两个数的二进制的和。0.1 的二进制是0.0001100110011001100...(1100 循环),0.2 的二进制是:0.00110011001100...(1100 循环),这两个数的二进制都是有限循环的数。那 JavaScript 是如何解决有限循环的二进制小数呢?

个别咱们认为数字包含整数和小数,然而在 JavaScript 中只有一种数字类型:Number,它的实现遵循 IEEE 754 规范,应用 64 位固定长度来示意,也就是规范的 double 双精度浮点数。在二进制迷信表示法中,双精度浮点数的小数局部最多只能保留 52 位,再加上后面的 1,其实就是保留 53 位有效数字,残余的须要舍去,听从“0 舍 1 入”的准则。

依据这个准则,0.1 和 0.2 的二进制数相加,再转化为十进制数就是:0.30000000000000004

上面看一下 双精度数是如何保留 的:

  • 第一局部(蓝色):用来存储符号位(sign),用来辨别正负数,0 示意负数,占用 1 位
  • 第二局部(绿色):用来存储指数(exponent),占用 11 位
  • 第三局部(红色):用来存储小数(fraction),占用 52 位

对于 0.1,它的二进制为:

0.00011001100110011001100110011001100110011001100110011001 10011...

转为迷信计数法(迷信计数法的后果就是浮点数):

1.1001100110011001100110011001100110011001100110011001*2^-4

能够看出 0.1 的符号位为 0,指数位为 -4,小数位为:

1001100110011001100110011001100110011001100110011001

那么问题又来了,指数位是正数,该如何保留 呢?

IEEE 标准规定了一个偏移量,对于指数局部,每次都加这个偏移量进行保留,这样即便指数是正数,那么加上这个偏移量也就是负数了。因为 JavaScript 的数字是双精度数,这里就以双精度数为例,它的指数局部为 11 位,能示意的范畴就是 0~2047,IEEE 固定 双精度数的偏移量为 1023

  • 当指数位不全是 0 也不全是 1 时(规格化的数值),IEEE 规定,阶码计算公式为 e-Bias。此时 e 最小值是 1,则 1 -1023= -1022,e 最大值是 2046,则 2046-1023=1023,能够看到,这种状况下取值范畴是-1022~1013
  • 当指数位全副是 0 的时候(非规格化的数值),IEEE 规定,阶码的计算公式为 1 -Bias,即 1 -1023= -1022。
  • 当指数位全副是 1 的时候(非凡值),IEEE 规定这个浮点数可用来示意 3 个非凡值,别离是正无穷,负无穷,NaN。具体的,小数位不为 0 的时候示意 NaN;小数位为 0 时,当符号位 s = 0 时示意正无穷,s= 1 时候示意负无穷。

对于下面的 0.1 的指数位为 -4,-4+1023 = 1019 转化为二进制就是:1111111011.

所以,0.1 示意为:

0 1111111011 1001100110011001100110011001100110011001100110011001

说了这么多,是时候该最开始的问题了,如何实现 0.1+0.2=0.3 呢?

对于这个问题,一个间接的解决办法就是设置一个误差范畴,通常称为“机器精度”。对 JavaScript 来说,这个值通常为 2 -52,在 ES6 中,提供了 Number.EPSILON 属性,而它的值就是 2 -52,只有判断 0.1+0.2-0.3 是否小于Number.EPSILON,如果小于,就能够判断为 0.1+0.2 ===0.3

function numberepsilon(arg1,arg2){return Math.abs(arg1 - arg2) < Number.EPSILON;        
}        

console.log(numberepsilon(0.1 + 0.2, 0.3)); // true

symbol 有什么用途

能够用来示意一个举世无双的变量避免命名抵触。然而面试官问还有吗?我没想出其余的用途就间接答我不晓得了,还能够利用 symbol 不会被惯例的办法(除了 Object.getOwnPropertySymbols 外)遍历到,所以能够用来模仿公有变量。

次要用来提供遍历接口,安排了 symbol.iterator 的对象才能够应用 for···of 循环,能够对立解决数据结构。调用之后回返回一个遍历器对象,蕴含有一个 next 办法,应用 next 办法后有两个返回值 value 和 done 别离示意函数以后执行地位的值和是否遍历结束。

Symbol.for() 能够在全局拜访 symbol

代码输入问题

function A(){}
function B(a){this.a = a;}
function C(a){if(a){this.a = a;}
}
A.prototype.a = 1;
B.prototype.a = 1;
C.prototype.a = 1;

console.log(new A().a);
console.log(new B().a);
console.log(new C(2).a);

输入后果:1 undefined 2

解析:

  1. console.log(new A().a),new A()为构造函数创立的对象,自身没有 a 属性,所以向它的原型去找,发现原型的 a 属性的属性值为 1,故该输入值为 1;
  2. console.log(new B().a),ew B()为构造函数创立的对象,该构造函数有参数 a,但该对象没有传参,故该输入值为 undefined;
  3. console.log(new C(2).a),new C()为构造函数创立的对象,该构造函数有参数 a,且传的实参为 2,执行函数外部,发现 if 为真,执行 this.a = 2, 故属性 a 的值为 2。

代码输入后果

function Foo(){Foo.a = function(){console.log(1);
    }
    this.a = function(){console.log(2)
    }
}

Foo.prototype.a = function(){console.log(3);
}

Foo.a = function(){console.log(4);
}

Foo.a();
let obj = new Foo();
obj.a();
Foo.a();

输入后果:4 2 1

解析:

  1. Foo.a() 这个是调用 Foo 函数的静态方法 a,尽管 Foo 中有优先级更高的属性办法 a,但 Foo 此时没有被调用,所以此时输入 Foo 的静态方法 a 的后果:4
  2. let obj = new Foo(); 应用了 new 办法调用了函数,返回了函数实例对象,此时 Foo 函数外部的属性办法初始化,原型链建设。
  3. obj.a() ; 调用 obj 实例上的办法 a,该实例上目前有两个 a 办法:一个是外部属性办法,另一个是原型上的办法。当这两者都存在时,首先查找 ownProperty,如果没有才去原型链上找,所以调用实例上的 a 输入:2
  4. Foo.a() ; 依据第 2 步可知 Foo 函数外部的属性办法已初始化,笼罩了同名的静态方法,所以输入:1

参考 前端进阶面试题具体解答

介绍 Loader

罕用 Loader:

  • file-loader: 加载文件资源,如 字体 / 图片 等,具备挪动 / 复制 / 命名等性能;
  • url-loader: 通常用于加载图片,能够将小图片间接转换为 Date Url,缩小申请;
  • babel-loader: 加载 js / jsx 文件,将 ES6 / ES7 代码转换成 ES5,抹平兼容性问题;
  • ts-loader: 加载 ts / tsx 文件,编译 TypeScript;
  • style-loader: 将 css 代码以 <style> 标签的模式插入到 html 中;
  • css-loader: 剖析 @import 和 url(),援用 css 文件与对应的资源;
  • postcss-loader: 用于 css 的兼容性解决,具备泛滥性能,例如 增加前缀,单位转换 等;
  • less-loader / sass-loader: css 预处理器,在 css 中新增了许多语法,进步了开发效率;

编写准则:

  • 繁多准则: 每个 Loader 只做一件事;
  • 链式调用: Webpack 会按程序链式调用每个 Loader;
  • 对立准则: 遵循 Webpack 制订的设计规定和构造,输出与输入均为字符串,各个 Loader 齐全独立,即插即用;

实现节流函数和防抖函数

函数防抖的实现:

function debounce(fn, wait) {
  var timer = null;

  return function() {
    var context = this,
      args = [...arguments];

    // 如果此时存在定时器的话,则勾销之前的定时器从新记时
    if (timer) {clearTimeout(timer);
      timer = null;
    }

    // 设置定时器,使事件间隔指定事件后执行
    timer = setTimeout(() => {fn.apply(context, args);
    }, wait);
  };
}

函数节流的实现:

// 工夫戳版
function throttle(fn, delay) {var preTime = Date.now();

  return function() {
    var context = this,
      args = [...arguments],
      nowTime = Date.now();

    // 如果两次工夫距离超过了指定工夫,则执行函数。if (nowTime - preTime >= delay) {preTime = Date.now();
      return fn.apply(context, args);
    }
  };
}

// 定时器版
function throttle (fun, wait){
  let timeout = null
  return function(){
    let context = this
    let args = [...arguments]
    if(!timeout){timeout = setTimeout(() => {fun.apply(context, args)
        timeout = null 
      }, wait)
    }
  }
}

Promise 的根本用法

(1)创立 Promise 对象

Promise 对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已胜利)和 rejected(已失败)。

Promise 构造函数承受一个函数作为参数,该函数的两个参数别离是 resolvereject

const promise = new Promise(function(resolve, reject) {
  // ... some code
  if (/* 异步操作胜利 */){resolve(value);
  } else {reject(error);
  }
});

个别状况下都会应用 new Promise() 来创立 promise 对象,然而也能够应用 promise.resolvepromise.reject这两个办法:

  • Promise.resolve

Promise.resolve(value)的返回值也是一个 promise 对象,能够对返回值进行.then 调用,代码如下:

Promise.resolve(11).then(function(value){console.log(value); // 打印出 11
});

resolve(11)代码中,会让 promise 对象进入确定 (resolve 状态),并将参数 11 传递给前面的 then 所指定的onFulfilled 函数;

创立 promise 对象能够应用 new Promise 的模式创建对象,也能够应用 Promise.resolve(value) 的模式创立 promise 对象;

  • Promise.reject

Promise.reject 也是 new Promise 的快捷模式,也创立一个 promise 对象。代码如下:

Promise.reject(new Error(“我错了,请原谅俺!!”));

就是上面的代码 new Promise 的简略模式:

new Promise(function(resolve,reject){reject(new Error("我错了!"));
});

上面是应用 resolve 办法和 reject 办法:

function testPromise(ready) {return new Promise(function(resolve,reject){if(ready) {resolve("hello world");
    }else {reject("No thanks");
    }
  });
};
// 办法调用
testPromise(true).then(function(msg){console.log(msg);
},function(error){console.log(error);
});

下面的代码的含意是给 testPromise 办法传递一个参数,返回一个 promise 对象,如果为 true 的话,那么调用 promise 对象中的 resolve() 办法,并且把其中的参数传递给前面的 then 第一个函数内,因而打印出“hello world”, 如果为 false 的话,会调用 promise 对象中的 reject() 办法,则会进入 then 的第二个函数内,会打印No thanks

(2)Promise 办法

Promise 有五个罕用的办法:then()、catch()、all()、race()、finally。上面就来看一下这些办法。

  1. then()

当 Promise 执行的内容合乎胜利条件时,调用 resolve 函数,失败就调用 reject 函数。Promise 创立完了,那该如何调用呢?

promise.then(function(value) {// success}, function(error) {// failure});

then办法能够承受两个回调函数作为参数。第一个回调函数是 Promise 对象的状态变为 resolved 时调用,第二个回调函数是 Promise 对象的状态变为 rejected 时调用。其中第二个参数能够省略。then办法返回的是一个新的 Promise 实例(不是原来那个 Promise 实例)。因而能够采纳链式写法,即 then 办法前面再调用另一个 then 办法。

当要写有程序的异步事件时,须要串行时,能够这样写:

let promise = new Promise((resolve,reject)=>{ajax('first').success(function(res){resolve(res);
    })
})
promise.then(res=>{return new Promise((resovle,reject)=>{ajax('second').success(function(res){resolve(res)
        })
    })
}).then(res=>{return new Promise((resovle,reject)=>{ajax('second').success(function(res){resolve(res)
        })
    })
}).then(res=>{})

那当要写的事件没有程序或者关系时,还如何写呢?能够应用all 办法来解决。

2. catch()

Promise 对象除了有 then 办法,还有一个 catch 办法,该办法相当于 then 办法的第二个参数,指向 reject 的回调函数。不过 catch 办法还有一个作用,就是在执行 resolve 回调函数时,如果呈现谬误,抛出异样,不会进行运行,而是进入 catch 办法中。

p.then((data) => {console.log('resolved',data);
},(err) => {console.log('rejected',err);
     }
); 
p.then((data) => {console.log('resolved',data);
}).catch((err) => {console.log('rejected',err);
});

3. all()

all办法能够实现并行任务,它接管一个数组,数组的每一项都是一个 promise 对象。当数组中所有的 promise 的状态都达到 resolved 的时候,all办法的状态就会变成 resolved,如果有一个状态变成了rejected,那么all 办法的状态就会变成rejected

javascript
let promise1 = new Promise((resolve,reject)=>{setTimeout(()=>{resolve(1);
    },2000)
});
let promise2 = new Promise((resolve,reject)=>{setTimeout(()=>{resolve(2);
    },1000)
});
let promise3 = new Promise((resolve,reject)=>{setTimeout(()=>{resolve(3);
    },3000)
});
Promise.all([promise1,promise2,promise3]).then(res=>{console.log(res);
    // 后果为:[1,2,3] 
})

调用 all 办法时的后果胜利的时候是回调函数的参数也是一个数组,这个数组按程序保留着每一个 promise 对象 resolve 执行时的值。

(4)race()

race办法和 all 一样,承受的参数是一个每项都是 promise 的数组,然而与 all 不同的是,当最先执行完的事件执行完之后,就间接返回该 promise 对象的值。如果第一个 promise 对象状态变成 resolved,那本身的状态变成了resolved;反之第一个promise 变成rejected,那本身状态就会变成rejected

let promise1 = new Promise((resolve,reject)=>{setTimeout(()=>{reject(1);
    },2000)
});
let promise2 = new Promise((resolve,reject)=>{setTimeout(()=>{resolve(2);
    },1000)
});
let promise3 = new Promise((resolve,reject)=>{setTimeout(()=>{resolve(3);
    },3000)
});
Promise.race([promise1,promise2,promise3]).then(res=>{console.log(res);
    // 后果:2
},rej=>{console.log(rej)};
)

那么 race 办法有什么理论作用呢?当要做一件事,超过多长时间就不做了,能够用这个办法来解决:

Promise.race([promise1,timeOutPromise(5000)]).then(res=>{})

5. finally()

finally办法用于指定不论 Promise 对象最初状态如何,都会执行的操作。该办法是 ES2018 引入规范的。

promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});

下面代码中,不论 promise 最初的状态,在执行完 thencatch指定的回调函数当前,都会执行 finally 办法指定的回调函数。

上面是一个例子,服务器应用 Promise 解决申请,而后应用 finally 办法关掉服务器。

server.listen(port)
  .then(function () {// ...})
  .finally(server.stop);

finally办法的回调函数不承受任何参数,这意味着没有方法晓得,后面的 Promise 状态到底是 fulfilled 还是 rejected。这表明,finally 办法外面的操作,应该是与状态无关的,不依赖于 Promise 的执行后果。finally实质上是 then 办法的特例:

promise
.finally(() => {// 语句});
// 等同于
promise
.then(
  result => {
    // 语句
    return result;
  },
  error => {
    // 语句
    throw error;
  }
);

下面代码中,如果不应用 finally 办法,同样的语句须要为胜利和失败两种状况各写一次。有了 finally 办法,则只须要写一次。

什么是尾调用,应用尾调用有什么益处?

尾调用指的是函数的最初一步调用另一个函数。代码执行是基于执行栈的,所以当在一个函数里调用另一个函数时,会保留以后的执行上下文,而后再新建另外一个执行上下文退出栈中。应用尾调用的话,因为曾经是函数的最初一步,所以这时能够不用再保留以后的执行上下文,从而节俭了内存,这就是尾调用优化。然而 ES6 的尾调用优化只在严格模式下开启,失常模式是有效的。

事件是什么?事件模型?

事件是用户操作网页时产生的交互动作,比方 click/move,事件除了用户触发的动作外,还能够是文档加载,窗口滚动和大小调整。事件被封装成一个 event 对象,蕴含了该事件产生时的所有相干信息(event 的属性)以及能够对事件进行的操作(event 的办法)。

事件是用户操作网页时产生的交互动作或者网页自身的一些操作,古代浏览器一共有三种事件模型:

  • DOM0 级事件模型,这种模型不会流传,所以没有事件流的概念,然而当初有的浏览器反对以冒泡的形式实现,它能够在网页中间接定义监听函数,也能够通过 js 属性来指定监听函数。所有浏览器都兼容这种形式。间接在 dom 对象上注册事件名称,就是 DOM0 写法。
  • IE 事件模型,在该事件模型中,一次事件共有两个过程,事件处理阶段和事件冒泡阶段。事件处理阶段会首先执行指标元素绑定的监听事件。而后是事件冒泡阶段,冒泡指的是事件从指标元素冒泡到 document,顺次查看通过的节点是否绑定了事件监听函数,如果有则执行。这种模型通过 attachEvent 来增加监听函数,能够增加多个监听函数,会按程序顺次执行。
  • DOM2 级事件模型,在该事件模型中,一次事件共有三个过程,第一个过程是事件捕捉阶段。捕捉指的是事件从 document 始终向下流传到指标元素,顺次查看通过的节点是否绑定了事件监听函数,如果有则执行。前面两个阶段和 IE 事件模型的两个阶段雷同。这种事件模型,事件绑定的函数是 addEventListener,其中第三个参数能够指定事件是否在捕捉阶段执行。

对执行上下文的了解

1. 执行上下文类型

(1)全局执行上下文

任何不在函数外部的都是全局执行上下文,它首先会创立一个全局的 window 对象,并且设置 this 的值等于这个全局对象,一个程序中只有一个全局执行上下文。

(2)函数执行上下文

当一个函数被调用时,就会为该函数创立一个新的执行上下文,函数的上下文能够有任意多个。

(3)eval函数执行上下文

执行在 eval 函数中的代码会有属于他本人的执行上下文,不过 eval 函数不常应用,不做介绍。

2. 执行上下文栈
  • JavaScript 引擎应用执行上下文栈来治理执行上下文
  • 当 JavaScript 执行代码时,首先遇到全局代码,会创立一个全局执行上下文并且压入执行栈中,每当遇到一个函数调用,就会为该函数创立一个新的执行上下文并压入栈顶,引擎会执行位于执行上下文栈顶的函数,当函数执行实现之后,执行上下文从栈中弹出,继续执行下一个上下文。当所有的代码都执行结束之后,从栈中弹出全局执行上下文。
let a = 'Hello World!';
function first() {console.log('Inside first function');
  second();
  console.log('Again inside first function');
}
function second() {console.log('Inside second function');
}
first();
// 执行程序
// 先执行 second(), 在执行 first()
3. 创立执行上下文

创立执行上下文有两个阶段:创立阶段 执行阶段

1)创立阶段

(1)this 绑定

  • 在全局执行上下文中,this 指向全局对象(window 对象)
  • 在函数执行上下文中,this 指向取决于函数如何调用。如果它被一个援用对象调用,那么 this 会被设置成那个对象,否则 this 的值被设置为全局对象或者 undefined

(2)创立词法环境组件

  • 词法环境是一种有 标识符——变量映射 的数据结构,标识符是指变量 / 函数名,变量是对理论对象或原始数据的援用。
  • 词法环境的外部有两个组件:加粗款式 :环境记录器: 用来贮存变量个函数申明的理论地位 外部环境的援用:能够拜访父级作用域

(3)创立变量环境组件

  • 变量环境也是一个词法环境,其环境记录器持有变量申明语句在执行上下文中创立的绑定关系。

2)执行阶段 此阶段会实现对变量的调配,最初执行完代码。

简略来说执行上下文就是指:

在执行一点 JS 代码之前,须要先解析代码。解析的时候会先创立一个全局执行上下文环境,先把代码中行将执行的变量、函数申明都拿进去,变量先赋值为 undefined,函数先申明好可应用。这一步执行完了,才开始正式的执行程序。

在一个函数执行之前,也会创立一个函数执行上下文环境,跟全局执行上下文相似,不过函数执行上下文会多出 this、arguments 和函数的参数。

  • 全局上下文:变量定义,函数申明
  • 函数上下文:变量定义,函数申明,thisarguments

手写 bind、apply、call

// call

Function.prototype.call = function (context, ...args) {
  context = context || window;

  const fnSymbol = Symbol("fn");
  context[fnSymbol] = this;

  context[fnSymbol](...args);
  delete context[fnSymbol];
}
// apply

Function.prototype.apply = function (context, argsArr) {
  context = context || window;

  const fnSymbol = Symbol("fn");
  context[fnSymbol] = this;

  context[fnSymbol](...argsArr);
  delete context[fnSymbol];
}
// bind

Function.prototype.bind = function (context, ...args) {
  context = context || window;
  const fnSymbol = Symbol("fn");
  context[fnSymbol] = this;

  return function (..._args) {args = args.concat(_args);

    context[fnSymbol](...args);
    delete context[fnSymbol];   
  }
}

对 rest 参数的了解

扩大运算符被用在函数形参上时,它还能够把一个拆散的参数序列整合成一个数组

function mutiple(...args) {
  let result = 1;
  for (var val of args) {result *= val;}
  return result;
}
mutiple(1, 2, 3, 4) // 24

这里,传入 mutiple 的是四个拆散的参数,然而如果在 mutiple 函数里尝试输入 args 的值,会发现它是一个数组:

function mutiple(...args) {console.log(args)
}
mutiple(1, 2, 3, 4) // [1, 2, 3, 4]

这就是 … rest 运算符的又一层威力了,它能够把函数的多个入参收敛进一个数组里。这一点 常常用于获取函数的多余参数,或者像下面这样解决函数参数个数不确定的状况。

原型批改、重写

function Person(name) {this.name = name}
// 批改原型
Person.prototype.getName = function() {}
var p = new Person('hello')
console.log(p.__proto__ === Person.prototype) // true
console.log(p.__proto__ === p.constructor.prototype) // true
// 重写原型
Person.prototype = {getName: function() {}}
var p = new Person('hello')
console.log(p.__proto__ === Person.prototype)        // true
console.log(p.__proto__ === p.constructor.prototype) // false

能够看到批改原型的时候 p 的构造函数不是指向 Person 了,因为间接给 Person 的原型对象间接用对象赋值时,它的构造函数指向的了根构造函数 Object,所以这时候p.constructor === Object,而不是p.constructor === Person。要想成立,就要用 constructor 指回来:

Person.prototype = {getName: function() {}}
var p = new Person('hello')
p.constructor = Person
console.log(p.__proto__ === Person.prototype)        // true
console.log(p.__proto__ === p.constructor.prototype) // true

强类型语言和弱类型语言的区别

  • 强类型语言:强类型语言也称为强类型定义语言,是一种总是强制类型定义的语言,要求变量的应用要严格合乎定义,所有变量都必须先定义后应用。Java 和 C ++ 等语言都是强制类型定义的,也就是说,一旦一个变量被指定了某个数据类型,如果不通过强制转换,那么它就永远是这个数据类型了。例如你有一个整数,如果不显式地进行转换,你不能将其视为一个字符串。
  • 弱类型语言:弱类型语言也称为弱类型定义语言,与强类型定义相同。JavaScript 语言就属于弱类型语言。简略了解就是一种变量类型能够被疏忽的语言。比方 JavaScript 是弱类型定义的,在 JavaScript 中就能够将字符串 ’12’ 和整数 3 进行连贯失去字符串 ’123’,在相加的时候会进行强制类型转换。

两者比照:强类型语言在速度上可能略逊色于弱类型语言,然而强类型语言带来的严谨性能够无效地帮忙防止许多谬误。

new 操作符的实现原理

new 操作符的执行过程:

(1)首先创立了一个新的空对象

(2)设置原型,将对象的原型设置为函数的 prototype 对象。

(3)让函数的 this 指向这个对象,执行构造函数的代码(为这个新对象增加属性)

(4)判断函数的返回值类型,如果是值类型,返回创立的对象。如果是援用类型,就返回这个援用类型的对象。

具体实现:

function objectFactory() {
  let newObject = null;
  let constructor = Array.prototype.shift.call(arguments);
  let result = null;
  // 判断参数是否是一个函数
  if (typeof constructor !== "function") {console.error("type error");
    return;
  }
  // 新建一个空对象,对象的原型为构造函数的 prototype 对象
  newObject = Object.create(constructor.prototype);
  // 将 this 指向新建对象,并执行函数
  result = constructor.apply(newObject, arguments);
  // 判断返回对象
  let flag = result && (typeof result === "object" || typeof result === "function");
  // 判断返回后果
  return flag ? result : newObject;
}
// 应用办法
objectFactory(构造函数, 初始化参数);

原函数形参定长(此时 fn.length 是个不变的常数)

// 写法 1 - 不保留参数, 递归部分函数
function curry(fn) {let judge = (...args) => {
        // 递归完结条件
        if(args.length === fn.length) return fn(...args);
        return (...arg) => judge(...args, ...arg);
    }
    return judge;
}

// 写法 2 - 保留参数, 递归整体函数
function curry(fn) {
    // 保留参数,除去第一个函数参数
    let presentArgs = [].slice.call(arguments, 1);
    // 返回一个新函数
    return function(){
        // 新函数调用时会持续传参
        let allArgs = [...presentArgs, ...arguments];
        // 递归完结条件
        if(allArgs.length === fn.length) {
            // 如果参数够了,就执行原函数
            return fn(,,,allArgs);
        }
        // 否则持续柯里化
        else return curry(fn, ...allArgs);
    }
}

// 测试
function add(a, b, c, d) {return a + b + c + d;}
console.log(add(1, 2, 3, 4));
let addCurry = curry(add);
// 以下后果都返回 10
console.log(addCurry(1)(2)(3)(4));  
console.log(addCurry(1)(2, 3, 4));
console.log(addCurry(1, 2)(3)(4));
console.log(addCurry(1, 2)(3, 4));
console.log(addCurry(1, 2, 3)(4));
console.log(addCurry(1, 2, 3, 4));

写版本号排序的办法

题目形容: 有一组版本号如下[‘0.1.1’, ‘2.3.3’, ‘0.302.1’, ‘4.2’, ‘4.3.5’, ‘4.3.4.5’]。当初须要对其进行排序,排序的后果为 [‘4.3.5′,’4.3.4.5′,’2.3.3′,’0.302.1′,’0.1.1’]

实现代码如下:

arr.sort((a, b) => {
  let i = 0;
  const arr1 = a.split(".");
  const arr2 = b.split(".");

  while (true) {const s1 = arr1[i];
    const s2 = arr2[i];
    i++;
    if (s1 === undefined || s2 === undefined) {return arr2.length - arr1.length;}

    if (s1 === s2) continue;

    return s2 - s1;
  }
});
console.log(arr);

什么是执行栈

能够把执行栈认为是一个存储函数调用的 栈构造,遵循先进后出的准则。当开始执行 JS 代码时,依据先进后出的准则,后执行的函数会先弹出栈,能够看到,foo 函数后执行,当执行结束后就从栈中弹出了。

平时在开发中,能够在报错中找到执行栈的痕迹:

function foo() {throw new Error('error')
}
function bar() {foo()
}
bar()

能够看到报错在 foo 函数,foo 函数又是在 bar 函数中调用的。当应用递归时,因为栈可寄存的函数是有 限度 的,一旦寄存了过多的函数且没有失去开释的话,就会呈现爆栈的问题

function bar() {  bar()}bar()
退出移动版