前端基础回顾之手写题
Posted code-is-poetry
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了前端基础回顾之手写题相关的知识,希望对你有一定的参考价值。
前言
本文还是依然针对前端重点基础知识点进行整体回顾系列的一篇,目标是帮助自己理解避免死记硬背。
下面针对new、Object.create、call、apply、new、bind 等基础API,从用法到原理实现过一遍,期望看完之后大家实现时不是死记硬背而是根据理解记忆推导。
基础准备
在探究上述内容原理之前,可以将上述API分为两类。
一类是new、Object.create这两者,涉及实例化对象的。
其对应的基础内容部分和上篇前端面试基础回顾之深入JS继承的基础部分相同。就是原型链和构造函数,这里不再赘述。
剩下的就是关于this指向的修改。
这里我们可以看下MDN中对this的描述。
this由调用时环境确定,简单总结如下:
- 显式指定:
-
new 实例化
this指向新构建的对象(new 显式返回一个对象,则this指向该返回对象,否则指向该对象实例)
// 例如 var bar = new foo()
-
bind、call、apply ,指向绑定对象
var bar = foo.call( obj2 )
- 隐式指定:
-
函数作为对象属性调用,即如object.func()形式,指向该对象。
//指向obj1 obj1.foo()
-
无指定
即不属于以上情况,为默认绑定。在strict mode下,就是undefined,否则是global对象。var fun1 = obj1.foo // this指向全局对象 fun1()
这里顺便把this指向也给过了一遍,以后遇到this指向,再复杂的都可以按照这个规律进行判断。
既然call、apply、new、bind具备修改this指向的功能,那么具体如何实现,就是下面要讨论的内容。
手写实现
new
用法
new 用法比较常见,举个MDN例子:
function Car(make, model, year)
this.make = make;
this.model = model;
this.year = year;
const car1 = new Car('Eagle', 'Talon TSi', 1993);
console.log(car1.make);
// expected output: "Eagle"
这里实例化了一个Car的实例对象car1,就不多说了。
分析
我们关注该方法功能是什么,然后由此推如何手写实现。
根据MDN的说法:
- 一个继承自 Foo.prototype 的新对象被创建。
- 使用指定的参数调用构造函数 Foo,并将 this 绑定到新创建的对象。new Foo 等同于 new Foo(),也就是没有指定参数列表,Foo 不带任何参数调用的情况。
- 由构造函数返回的对象就是 new 表达式的结果。如果构造函数没有显式返回一个对象,则使用步骤1创建的对象。(一般情况下,构造函数不返回值,但是用户可以选择主动返回对象,来覆盖正常的对象创建步骤)
我们要实现的点主要也有两个:
- 实现一个新对象
- 继承F的属性
- 绑定this
如何实现上述两点,就用到我们的基础知识了。
- 新建对象,这个显然都会。
- 继承F的属性
这里说继承可能不如说赋值更好理解一些。
对于一个构造函数来说,属性包括两部分实例属性和原型属性。
新对象要继承其原型属性,修改原型链指向即可。
继承实例属性,将构造函数F的this指向新对象,并执行一次就实现了对新对象的赋值。该过程顺便还实现了this的绑定。
结合该思路一起来看看实现思路
实现
初版实现:
// 1.首先声明函数my_new
function my_new(func)
// 2. 新建对象
var o =
// 3. 修改原型链
o._proto_ = func.prototype
// 示例属性获取,并修改this
func.call(o)
// 返回对象
return o
根据分析自然就实现了上面的代码。
不过new 还有个点分析时上面没有提到,由构造函数返回的对象就是 new 表达式的结果。如果构造函数没有显式返回一个对象,则使用步骤1创建的对象。
假如构造函数返回了对象,那么需要进行判断func执行的结果是不是对象,不能直接返回执行结果。
// 1.首先声明函数my_new
function my_new(func)
// 2. 新建对象
var o =
// 3. 修改原型链
o._proto_ = func.prototype
// 示例属性获取,并修改this
// 获取构造函数执行结果,判断是否有显式返回。
var res = func.call(o)
// 视res类型决定返回对象
return typeof res === "object" ?res : o
到这里new 的实现就完成了。
Object.create
用法
该方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。即基于现有对象创建一个新的对象,直接看代码比较直接:
const person =
isHuman: false,
printIntroduction: function ()
console.log(`My name is $this.name. Am I human? $this.isHuman`);
;
const me = Object.create(person);
me.name = "Matthew"; // "name" is a property set on "me", but not on "person"
me.isHuman = true; // inherited properties can be overwritten
me.printIntroduction();
// expected output: "My name is Matthew. Am I human? true"
分析
该方法的功能在于两点:
- 一个新对象
- 带有指定的原型对象和属性
结合上述,倒序来分析:
- 带有指定的原型对象和属性
这里比较特殊,因为原始对象不是构造函数,要继承其所有属性的话,还是要借助构造函数来实现,即原始对象person,作为新构造函数F的原型对象,新对象me是F的实例。 -
一个新对象
新的对象可以是字面量声明,也可以通过使用new来实例化。这里就是后者了。这也是倒序分析的原因。实现
// 1. 声明函数
function create(Obj)
// 2. 新建构造函数
function F()
// 3. 原型链修改
F.prototype = Obj
// 4.新建对象
return new F()
至于ES6正式规范中还是可以第二个参数的情况暂时不补充,我也没有见到比较好的实现,大家可以补充。
call和apply
这两者用法和实现差别不大,就放一起分析了。
用法
采用W3C的例子
//call 用法
var person =
firstName:"Steve",
lastName: "Jobs",
fullName: function()
return this.firstName + " " + this.lastName;
var person1 =
firstName:"Bill",
lastName: "Gates",
person.fullName.call(person1); // "Bill Gates"
// apply 用法
person.fullName.apply(person1); // 将返回 "Bill Gates"
这里没有体现出两者差别,差别在于传参的不同。
-
call() 方法分别接受参数。
-
apply() 方法接受数组形式的参数。
分析
call函数的功能有如下几点:
- 改变函数中this指向
-
获取后续参数则并执行
针对以上两点,主要在于如何改变this指向。
- 回顾准备里面的内容,改变this指向的方法,除去显式的,我们也只剩下作为对象属性调用了。
即将函数赋值给被调用对象,作为其属性方法执行,至于参数执行时调用就好。
不过这里有些点要注意
- 我们给被调用对象增加属性,执行完毕之后还是要删除的,避免与其他操作。
- 同样增加属性时,属性名也要注意避免冲突,最好直接使用Symbol。
实现
call的实现:
// 函数
Function.prototype._call = function (ctx)
// 1. 构造被调用对象,兼容默认值
var obj = ctx || window
// 2. 获取后续参数
var args = Array.from(arguments).slice(1)
// 3. 获取唯一属性名
var fun = Symbol()
// 4. 增加属性方法,指向待调用函数
obj[fun] = this
var result = obj[fun](...args)
// 5. 执行完毕后,删除该属性
delete obj[fun]
return result
apply实现与call很类似只是参数处理有些差别。
Function.prototype._apply = function (ctx)
var obj = ctx || window
var args = Array.from(arguments).slice(1)
var fun = Symbol()
// 参数处理
var result = obj[fun](args.join(','))
delete obj[fun]
return result
bind
用法
MDN描述:bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。
使用方式如下:
const module =
x: 42,
getX: function()
return this.x;
const unboundGetX = module.getX;
console.log(unboundGetX()); // The function gets invoked at the global scope
// expected output: undefined
const boundGetX = unboundGetX.bind(module);
console.log(boundGetX());// expected output: 42
分析
其功能分为如下几点:
- 修改this指向到指定对象
- 返回函数,可被后续执行
- 参数处理,较简单
解决思路:
- this指向
因为call等的实现,这里就可以偷懒了,使用apply来实现 - 返回一个函数
常见的闭包形式 - 参数处理
可能稍微复杂点的在于,执行时要考虑后续的参数拼接。
实现
初版实现:
Function.prototype._bind = function(ctx)
// 1. 兼容判断
var ctx = ctx || window
// 2. 保留当前获取参数
var args = Array.from(arguments).slice(1)
var _this = this
// 3. 返回函数
return function F (arguments)
// 4. 绑定this指向,拼接新增参数
return _this.apply(ctx,args.concat(arguments))
上述完成了初版的功能要求,但是bind还有一种情况,返回的毕竟是个函数,就可以当做构造函数与new 结合使用。
function Point(x, y)
this.x = x;
this.y = y;
Point.prototype.toString = function ()
return this.x + ',' + this.y;
;
var emptyObj = ;
var YAxisPoint = Point.bind(emptyObj, 0/*x*/);
var axisPoint = new YAxisPoint(5);
axisPoint.toString(); // '0,5' 此时this指向当前示例对象,而非emptyObj
这种场景下,为什么this指向了实例对象,主要是new 本身的功能体现。
而我们的api要支持new 的情况还是要结合new 的功能来看。
new 通过调用构造函数,产生了一个示例对象。主要是下面这段代码。
var res = func.call(o)
结合到我们的call中,此时func即为我们return 的F函数。
即此时函数中的this 为F的示例,由此可以区分两种场景。
Function.prototype._bind = function (ctx)
// 1. 兼容判断
var ctx = ctx || window
// 2. 保留当前获取参数
var args = Array.from(arguments).slice(1)
var _this = this
// 3. 返回函数
return function F(arguments)
// 4.判断是否new 场景
if(this instanceof F)
// 5. 此时直接执行构造函数
return new _this(...args, ...arguments)
else
// 5. 常规场景,依然绑定this指向,拼接新增参数
return _this.apply(ctx, args.concat(arguments))
结束语
到这里,几个简单的手写题就总结完毕了,上面的例子多出自MDN。当然上面的代码都存在一个问题就是对于异常的处理。这里就不列出了,大家可以自行补充。对于前面提到的js继承的基础,可以看我前面的文章。还是同样一句话共勉你我,你若盛开蝴蝶自来。
如水穿石,厚积才可薄发
以上是关于前端基础回顾之手写题的主要内容,如果未能解决你的问题,请参考以下文章