手写题:实现柯里化
预先设置一些参数
柯里化是什么:是指这样一个函数,它接收函数 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的指向、原型、原型链、类的继承、数据类型等。
解析:
- parent.show(),可以直接获得所需的值,没啥好说的;
- child1.show(),
Child
的构造函数原本是指向Child
的,题目显式将Child
类的原型对象指向了Parent
类的一个实例,需要注意Child.prototype
指向的是Parent
的实例parent
,而不是指向Parent
这个类。 - child2.show(),这个也没啥好说的;
- parent.show(),
parent
是一个Parent
类的实例,Child.prorotype
指向的是Parent
类的另一个实例,两者在堆内存中互不影响,所以上述操作不影响parent
实例,所以输出结果不变; - child1.show(),
child1
执行了change()
方法后,发生了怎样的变化呢? - this.b.push(this.a),由于this的动态指向特性,this.b会指向
Child.prototype
上的b数组,this.a会指向child1
的a属性,所以Child.prototype.b
变成了[1,2,1,11]; - this.a = this.b.length,这条语句中
this.a
和this.b
的指向与上一句一致,故结果为child1.a
变为4; - this.c.demo = this.a++,由于
child1
自身属性并没有c这个属性,所以此处的this.c
会指向Child.prototype.c
,this.a
值为4,为原始类型,故赋值操作时会直接赋值,Child.prototype.c.demo
的结果为4,而this.a
随后自增为5(4 + 1 = 5)。 child2
执行了change()
方法, 而child2
和child1
均是Child
类的实例,所以他们的原型链指向同一个原型对象Child.prototype
,也就是同一个parent
实例,所以child2.change()
中所有影响到原型对象的语句都会影响child1
的最终输出结果。- this.b.push(this.a),由于this的动态指向特性,this.b会指向
Child.prototype
上的b数组,this.a会指向child2
的a属性,所以Child.prototype.b
变成了[1,2,1,11,12]; - this.a = this.b.length,这条语句中
this.a
和this.b
的指向与上一句一致,故结果为child2.a
变为5; - 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
解析:
- 最关键的就是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。
- 当指向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
解析:
- 第一次执行fn(),this指向window对象,输出10。
- 第二次执行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
解析:
- 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
代码输出结果
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
解析:
- 首先func是由myObject调用的,this指向myObject。又因为var self = this;所以self指向myObject。
- 这个立即执行匿名函数表达式是由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);
}
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。