手写题:实现柯里化

事后设置一些参数

柯里化是什么:是指这样一个函数,它接管函数 A,并且能返回一个新的函数,这个新的函数可能处理函数 A 的残余参数

function createCurry(func, args) {  var argity = func.length;  var args = args || [];  return function () {    var _args = [].slice.apply(arguments);    args.push(..._args);    if (args.length < argity) {      return createCurry.call(this, func, args);    }    return func.apply(this, args);  }}

代码输入后果

function Dog() {  this.name = 'puppy'}Dog.prototype.bark = () => {  console.log('woof!woof!')}const dog = new Dog()console.log(Dog.prototype.constructor === Dog && dog.constructor === Dog && dog instanceof Dog)

输入后果:true

解析: 因为constructor是prototype上的属性,所以dog.constructor实际上就是指向Dog.prototype.constructor;constructor属性指向构造函数。instanceof而理论检测的是类型是否在实例的原型链上。

constructor是prototype上的属性,这一点很容易被疏忽掉。constructor和instanceof 的作用是不同的,理性地来说,constructor的限度比拟严格,它只能严格比照对象的构造函数是不是指定的值;而instanceof比拟涣散,只有检测的类型在原型链上,就会返回true。

写代码:实现函数可能深度克隆根本类型

浅克隆:

function shallowClone(obj) {  let cloneObj = {};  for (let i in obj) {    cloneObj[i] = obj[i];  }  return cloneObj;}

深克隆:

  • 思考根底类型
  • 援用类型

    • RegExp、Date、函数 不是 JSON 平安的
    • 会失落 constructor,所有的构造函数都指向 Object
    • 破解循环援用
function deepCopy(obj) {  if (typeof obj === 'object') {    var result = obj.constructor === Array ? [] : {};    for (var i in obj) {      result[i] = typeof obj[i] === 'object' ? deepCopy(obj[i]) : obj[i];    }  } else {    var result = obj;  }  return result;}

说一下原型链和原型链的继承吧

  • 所有一般的 [[Prototype]] 链最终都会指向内置的 Object.prototype,其蕴含了 JavaScript 中许多通用的性能
  • 为什么能创立 “类”,借助一种非凡的属性:所有的函数默认都会领有一个名为 prototype 的共有且不可枚举的属性,它会指向另外一个对象,这个对象通常被称为函数的原型
function Person(name) {  this.name = name;}Person.prototype.constructor = Person
  • 在产生 new 结构函数调用时,会将创立的新对象的 [[Prototype]] 链接到 Person.prototype 指向的对象,这个机制就被称为原型链继承
  • 办法定义在原型上,属性定义在构造函数上
  • 首先要说一下 JS 原型和实例的关系:每个构造函数 (constructor)都有一个原型对象(prototype),这个原型对象蕴含一个指向此构造函数的指针属性,通过 new 进行结构函数调用生成的实例,此实例蕴含一个指向原型对象的指针,也就是通过 [[Prototype]] 链接到了这个原型对象
  • 而后说一下 JS 中属性的查找:当咱们试图援用实例对象的某个属性时,是依照这样的形式去查找的,首先查找实例对象上是否有这个属性,如果没有找到,就去结构这个实例对象的构造函数的 prototype 所指向的对象下来查找,如果还找不到,就从这个 prototype 对象所指向的构造函数的 prototype 原型对象下来查找
  • 什么是原型链:这样逐级查找形似一个链条,且通过 [[Prototype]] 属性链接,所以被称为原型链
  • 什么是原型链继承,类比类的继承:当有两个构造函数 A 和 B,将一个构造函数 A 的原型对象的,通过其 [[Prototype]] 属性链接到另外一个 B 构造函数的原型对象时,这个过程被称之为原型继承。

标准答案更正确的解释

什么是原型链?

当对象查找一个属性的时候,如果没有在本身找到,那么就会查找本身的原型,如果原型还没有找到,那么会持续查找原型的原型,直到找到 Object.prototype 的原型时,此时原型为 null,查找进行。
这种通过 通过原型链接的逐级向上的查找链被称为原型链

什么是原型继承?

一个对象能够应用另外一个对象的属性或者办法,就称之为继承。具体是通过将这个对象的原型设置为另外一个对象,这样依据原型链的规定,如果查找一个对象属性且在本身不存在时,就会查找另外一个对象,相当于一个对象能够应用另外一个对象的属性和办法了。

代码输入问题

function Parent() {    this.a = 1;    this.b = [1, 2, this.a];    this.c = { demo: 5 };    this.show = function () {        console.log(this.a , this.b , this.c.demo );    }}function Child() {    this.a = 2;    this.change = function () {        this.b.push(this.a);        this.a = this.b.length;        this.c.demo = this.a++;    }}Child.prototype = new Parent();var parent = new Parent();var child1 = new Child();var child2 = new Child();child1.a = 11;child2.a = 12;parent.show();child1.show();child2.show();child1.change();child2.change();parent.show();child1.show();child2.show();

输入后果:

parent.show(); // 1  [1,2,1] 5child1.show(); // 11 [1,2,1] 5child2.show(); // 12 [1,2,1] 5parent.show(); // 1 [1,2,1] 5child1.show(); // 5 [1,2,1,11,12] 5child2.show(); // 6 [1,2,1,11,12] 5

这道题目值得神帝,他波及到的知识点很多,例如this的指向、原型、原型链、类的继承、数据类型等。

解析:

  1. parent.show(),能够间接取得所需的值,没啥好说的;
  2. child1.show(),Child的构造函数本来是指向Child的,题目显式将Child类的原型对象指向了Parent类的一个实例,须要留神Child.prototype指向的是Parent的实例parent,而不是指向Parent这个类。
  3. child2.show(),这个也没啥好说的;
  4. parent.show(),parent是一个Parent类的实例,Child.prorotype指向的是Parent类的另一个实例,两者在堆内存中互不影响,所以上述操作不影响parent实例,所以输入后果不变;
  5. child1.show(),child1执行了change()办法后,产生了怎么的变动呢?
  6. this.b.push(this.a),因为this的动静指向个性,this.b会指向Child.prototype上的b数组,this.a会指向child1a属性,所以Child.prototype.b变成了[1,2,1,11];
  7. this.a = this.b.length,这条语句中this.athis.b的指向与上一句统一,故后果为child1.a变为4;
  8. this.c.demo = this.a++,因为child1本身属性并没有c这个属性,所以此处的this.c会指向Child.prototype.cthis.a值为4,为原始类型,故赋值操作时会间接赋值,Child.prototype.c.demo的后果为4,而this.a随后自增为5(4 + 1 = 5)。
  9. child2执行了change()办法, 而child2child1均是Child类的实例,所以他们的原型链指向同一个原型对象Child.prototype,也就是同一个parent实例,所以child2.change()中所有影响到原型对象的语句都会影响child1的最终输入后果。
  10. this.b.push(this.a),因为this的动静指向个性,this.b会指向Child.prototype上的b数组,this.a会指向child2a属性,所以Child.prototype.b变成了[1,2,1,11,12];
  11. this.a = this.b.length,这条语句中this.athis.b的指向与上一句统一,故后果为child2.a变为5;
  12. this.c.demo = this.a++,因为child2本身属性并没有c这个属性,所以此处的this.c会指向Child.prototype.c,故执行后果为Child.prototype.c.demo的值变为child2.a的值5,而child2.a最终自增为6(5 + 1 = 6)。

代码输入后果

function a(xx){  this.x = xx;  return this};var x = a(5);var y = a(6);console.log(x.x)  // undefinedconsole.log(y.x)  // 6

输入后果: undefined 6

解析:

  1. 最要害的就是var x = a(5),函数a是在全局作用域调用,所以函数外部的this指向window对象。所以 this.x = 5 就相当于:window.x = 5。之后 return this,也就是说 var x = a(5) 中的x变量的值是window,这里的x将函数外部的x的值笼罩了。而后执行console.log(x.x), 也就是console.log(window.x),而window对象中没有x属性,所以会输入undefined。
  2. 当指向y.x时,会给全局变量中的x赋值为6,所以会打印出6。

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

代码输入后果

var length = 10;function fn() {    console.log(this.length);}var obj = {  length: 5,  method: function(fn) {    fn();    arguments[0]();  }};obj.method(fn, 1);

输入后果: 10 2

解析:

  1. 第一次执行fn(),this指向window对象,输入10。
  2. 第二次执行arguments[0],相当于arguments调用办法,this指向arguments,而这里传了两个参数,故输入arguments长度为2。

代码输入后果

var friendName = 'World';(function() {  if (typeof friendName === 'undefined') {    var friendName = 'Jack';    console.log('Goodbye ' + friendName);  } else {    console.log('Hello ' + friendName);  }})();

输入后果:Goodbye Jack

咱们晓得,在 JavaScript中, Function 和 var 都会被晋升(变量晋升),所以下面的代码就相当于:

var name = 'World!';(function () {    var name;    if (typeof name === 'undefined') {        name = 'Jack';        console.log('Goodbye ' + name);    } else {        console.log('Hello ' + name);    }})();

这样,答案就高深莫测了。

代码输入后果

Promise.resolve().then(() => {    console.log('1');    throw 'Error';}).then(() => {    console.log('2');}).catch(() => {    console.log('3');    throw 'Error';}).then(() => {    console.log('4');}).catch(() => {    console.log('5');}).then(() => {    console.log('6');});

执行后果如下:

1 3 5 6

在这道题目中,咱们须要晓得,无论是thne还是catch中,只有throw 抛出了谬误,就会被catch捕捉,如果没有throw出谬误,就被继续执行前面的then。

代码输入后果

function runAsync (x) {    const p = new Promise(r => setTimeout(() => r(x, console.log(x)), 1000))    return p}Promise.all([runAsync(1), runAsync(2), runAsync(3)]).then(res => console.log(res))

输入后果如下:

123[1, 2, 3]

首先,定义了一个Promise,来异步执行函数runAsync,该函数传入一个值x,而后距离一秒后打印出这个x。

之后再应用Promise.all来执行这个函数,执行的时候,看到一秒之后输入了1,2,3,同时输入了数组[1, 2, 3],三个函数是同步执行的,并且在一个回调函数中返回了所有的后果。并且后果和函数的执行程序是统一的。

代码输入后果

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

代码输入后果

function foo(something){    this.a = something}var obj1 = {}var bar = foo.bind(obj1);bar(2);console.log(obj1.a); // 2var baz = new bar(3);console.log(obj1.a); // 2console.log(baz.a); // 3

输入后果: 2 2 3

这道题目和下面题目差不多,次要都是考查this绑定的优先级。记住以下论断即可:this绑定的优先级:new绑定 > 显式绑定 > 隐式绑定 > 默认绑定。

代码输入后果

function fn1(){  console.log('fn1')}var fn2fn1()fn2()fn2 = function() {  console.log('fn2')}fn2()

输入后果:

fn1Uncaught TypeError: fn2 is not a functionfn2

这里也是在考查变量晋升,关键在于第一个fn2(),这时fn2仍是一个undefined的变量,所以会报错fn2不是一个函数。

JS 隐式转换,显示转换

个别非根底类型进行转换时会先调用 valueOf,如果 valueOf 无奈返回根本类型值,就会调用 toString

字符串和数字

  • "+" 操作符,如果有一个为字符串,那么都转化到字符串而后执行字符串拼接
  • "-" 操作符,转换为数字,相减 (-a, a * 1 a/1) 都能进行隐式强制类型转换
[] + {} 和 {} + []

布尔值到数字

  • 1 + true = 2
  • 1 + false = 1

转换为布尔值

  • for 中第二个
  • while
  • if
  • 三元表达式
  • || (逻辑或) && (逻辑与)右边的操作数

符号

  • 不能被转换为数字
  • 能被转换为布尔值(都是 true)
  • 能够被转换成字符串 "Symbol(cool)"

宽松相等和严格相等

宽松相等容许进行强制类型转换,而严格相等不容许

字符串与数字

转换为数字而后比拟

其余类型与布尔类型

  • 先把布尔类型转换为数字,而后持续进行比拟

对象与非对象

  • 执行对象的 ToPrimitive(对象)而后持续进行比拟

假值列表

  • undefined
  • null
  • false
  • +0, -0, NaN
  • ""

代码输入后果

var myObject = {    foo: "bar",    func: function() {        var self = this;        console.log(this.foo);          console.log(self.foo);          (function() {            console.log(this.foo);              console.log(self.foo);          }());    }};myObject.func();

输入后果:bar bar undefined bar

解析:

  1. 首先func是由myObject调用的,this指向myObject。又因为var self = this;所以self指向myObject。
  2. 这个立刻执行匿名函数表达式是由window调用的,this指向window 。立刻执行匿名函数的作用域处于myObject.func的作用域中,在这个作用域找不到self变量,沿着作用域链向上查找self变量,找到了指向 myObject对象的self。

说下对 JS 的理解吧

是基于原型的动静语言,次要独特个性有 this、原型和原型链。

JS 严格意义上来说分为:语言规范局部(ECMAScript)+ 宿主环境局部

语言规范局部

2015 年公布 ES6,引入诸多新个性使得可能编写大型项目变成可能,规范自 2015 之后以年号代号,每年一更

宿主环境局部

  • 在浏览器宿主环境包含 DOM + BOM 等
  • 在 Node,宿主环境包含一些文件、数据库、网络、与操作系统的交互等

代码输入后果

var a = 10var obj = {  a: 20,  say: () => {    console.log(this.a)  }}obj.say() var anotherObj = { a: 30 } obj.say.apply(anotherObj) 

输入后果:10 10

我么晓得,箭头函数时不绑定this的,它的this来自原其父级所处的上下文,所以首先会打印全局中的 a 的值10。前面尽管让say办法指向了另外一个对象,然而仍不能扭转箭头函数的个性,它的this依然是指向全局的,所以依旧会输入10。

然而,如果是一般函数,那么就会有齐全不一样的后果:

var a = 10  var obj = {    a: 20,    say(){    console.log(this.a)    }  }  obj.say()   var anotherObj={a:30}   obj.say.apply(anotherObj)

输入后果:20 30

这时,say办法中的this就会指向他所在的对象,输入其中的a的值。

说一下你对盒模型的了解?

CSS3中的盒模型有以下两种:规范盒模型、IE盒模型盒模型都是由四个局部组成的,别离是margin、border、padding和content规范盒模型和IE盒模型的区别在于设置width和height时, 所对应的范畴不同1、规范盒模型的width和height属性的范畴只蕴含了content2、IE盒模型的width和height属性的范畴蕴含了border、padding和content能够通过批改元素的box-sizing属性来扭转元素的盒模型;1、box-sizing:content-box示意规范盒模型(默认值)2、box-sizing:border-box示意IE盒模型(怪异盒模型)

代码输入后果

var obj = {   name : 'cuggz',   fun : function(){     console.log(this.name);   } } obj.fun()     // cuggznew obj.fun() // undefined

应用new构造函数时,其this指向的是全局环境window。

事件循环机制 (Event Loop)

事件循环机制从整体上通知了咱们 JavaScript 代码的执行程序 Event Loop即事件循环,是指浏览器或Node的一种解决javaScript单线程运行时不会阻塞的一种机制,也就是咱们常常应用异步的原理。

先执行 Script 脚本,而后清空微工作队列,而后开始下一轮事件循环,持续先执行宏工作,再清空微工作队列,如此往返。

  • 宏工作:Script/setTimeout/setInterval/setImmediate/ I/O / UI Rendering
  • 微工作:process.nextTick()/Promise

上诉的 setTimeout 和 setInterval 等都是工作源,真正进入工作队列的是他们散发的工作。

优先级

  • setTimeout = setInterval 一个队列
  • setTimeout > setImmediate
  • process.nextTick > Promise
for (const macroTask of macroTaskQueue) {    handleMacroTask();      for (const microTask of microTaskQueue) {          handleMicroTask(microTask);    }}