关于前端:前端面试比较好的回答

3次阅读

共计 9071 个字符,预计需要花费 23 分钟才能阅读完成。

手写题:实现柯里化

事后设置一些参数

柯里化是什么:是指这样一个函数,它接管函数 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] 5

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

parent.show(); // 1 [1,2,1] 5

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

child2.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)  // undefined
console.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))

输入后果如下:

1
2
3
[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); // 2

var baz = new bar(3);
console.log(obj1.a); // 2
console.log(baz.a); // 3

输入后果:2 2 3

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

代码输入后果

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

fn1()
fn2()

fn2 = function() {console.log('fn2')
}

fn2()

输入后果:

fn1
Uncaught TypeError: fn2 is not a function
fn2

这里也是在考查变量晋升,关键在于第一个 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 = 10
var 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 属性的范畴只蕴含了 content
2、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()     // cuggz
new 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);  
  }
}
正文完
 0