代码输入后果
function Person(name) { this.name = name}var p2 = new Person('king');console.log(p2.__proto__) //Person.prototypeconsole.log(p2.__proto__.__proto__) //Object.prototypeconsole.log(p2.__proto__.__proto__.__proto__) // nullconsole.log(p2.__proto__.__proto__.__proto__.__proto__)//null前面没有了,报错console.log(p2.__proto__.__proto__.__proto__.__proto__.__proto__)//null前面没有了,报错console.log(p2.constructor)//Personconsole.log(p2.prototype)//undefined p2是实例,没有prototype属性console.log(Person.constructor)//Function 一个空函数console.log(Person.prototype)//打印出Person.prototype这个对象里所有的办法和属性console.log(Person.prototype.constructor)//Personconsole.log(Person.prototype.__proto__)// Object.prototypeconsole.log(Person.__proto__) //Function.prototypeconsole.log(Function.prototype.__proto__)//Object.prototypeconsole.log(Function.__proto__)//Function.prototypeconsole.log(Object.__proto__)//Function.prototypeconsole.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.2console.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
解析:
- console.log(new A().a),new A()为构造函数创立的对象,自身没有a属性,所以向它的原型去找,发现原型的a属性的属性值为1,故该输入值为1;
- console.log(new B().a),ew B()为构造函数创立的对象,该构造函数有参数a,但该对象没有传参,故该输入值为undefined;
- 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
解析:
- Foo.a() 这个是调用 Foo 函数的静态方法 a,尽管 Foo 中有优先级更高的属性办法 a,但 Foo 此时没有被调用,所以此时输入 Foo 的静态方法 a 的后果:4
- let obj = new Foo(); 应用了 new 办法调用了函数,返回了函数实例对象,此时 Foo 函数外部的属性办法初始化,原型链建设。
- obj.a() ; 调用 obj 实例上的办法 a,该实例上目前有两个 a 办法:一个是外部属性办法,另一个是原型上的办法。当这两者都存在时,首先查找 ownProperty ,如果没有才去原型链上找,所以调用实例上的 a 输入:2
- 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构造函数承受一个函数作为参数,该函数的两个参数别离是resolve
和reject
。
const promise = new Promise(function(resolve, reject) { // ... some code if (/* 异步操作胜利 */){ resolve(value); } else { reject(error); }});
个别状况下都会应用new Promise()
来创立promise对象,然而也能够应用promise.resolve
和promise.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。上面就来看一下这些办法。
- 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
。
javascriptlet 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
最初的状态,在执行完then
或catch
指定的回调函数当前,都会执行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和函数的参数。
- 全局上下文:变量定义,函数申明
- 函数上下文:变量定义,函数申明,
this
,arguments
手写 bind、apply、call
// callFunction.prototype.call = function (context, ...args) { context = context || window; const fnSymbol = Symbol("fn"); context[fnSymbol] = this; context[fnSymbol](...args); delete context[fnSymbol];}
// applyFunction.prototype.apply = function (context, argsArr) { context = context || window; const fnSymbol = Symbol("fn"); context[fnSymbol] = this; context[fnSymbol](...argsArr); delete context[fnSymbol];}
// bindFunction.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) // trueconsole.log(p.__proto__ === p.constructor.prototype) // true// 重写原型Person.prototype = { getName: function() {}}var p = new Person('hello')console.log(p.__proto__ === Person.prototype) // trueconsole.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 = Personconsole.log(p.__proto__ === Person.prototype) // trueconsole.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);// 以下后果都返回 10console.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()