前言
一年经验侥幸拿到字节跳动Offer,最近面了那么4,5家,发现无论大公司还是小公司,都比较注重原生Javascript,而大点的公司框架源码更是必问。所以我重新看了遍MDN以及其他的书籍,自己总结出了如下的题目,目的是提升对底层原生知识的了解,其实我自己基础也不是特别扎实,反正写出来大家可以一起讨论~
题目
- 请写一个函数
JudgePropsOnlyInPrototype(obj, key)
判断key这个属性是否仅仅存在于obj对象的原型上
function JudgePropsOnlyInPrototype(obj, key){ var hasOwn = Object.hasOwnProperty; return (key in obj) && !hasOwn.call(obj,key)}复制代码
解释: return
中in
运算符判定了key
是否在对象本身或者其原型上,而后面的!hasOwn
则判定了key
不在对象本身上,则2者相结合就能够判定出key
是否只在原型上,注意这里用了call
而不是直接obj.hasOwnProperty(key)
,这是因为某些对象根本没有hasOwnProperty
这个方法,比如Object.create(null)
,这个对象完全纯净和空白,没有任何原型上的方法和属性,所以这样写健壮性高一些
- 请解释Js中静态方法,实例方法,原型方法的区别和使用场景
静态方法:
function Animal(type){ this.type = type}Animal.staticMethod = function(){ console.log("I'm a static method!")}Animal.staticMethod()复制代码
简而言之,静态方法(static method)就是定义在构造函数上或者类本身上的方法,这类方法无法通过类的实例来访问,在方法内部无法通过this来访问类内部的变量,那么你可能会问什么样的方法要被定义为静态方法,那就是不需要访问类属性的方法都可以写成静态的,这类方法一般都是 Helper 方法,即对输入进行处理再得到一个输出,与对象的成员无关,比如Math.abs
这种就是静态方法,继续深入思考为什么要有静态方法,原因我觉得就是节约内存使用量,因为静态方法可以所有实例公用,不用再放到每一个实例或者其原型上
function Animal(type){ this.type = type this.run = function(){ console.log('run') }}var animal = new Animal()animal.run()复制代码
上面的run就是实例方法,实例方法就是一个类new出来的实例上的方法,这个方法存在于实例对象本身上,在构造函数中通过this定义,一般来说定义一个方法为实例方法是要求这个方法是该实例独有的,不和其他实例共享,否则就应该定义为原型方法
原型方法:function Animal(type){ this.type = type}Animal.prototype.run = function(){ console.log('run')}var animal = new Animal()animal.run()复制代码
这个run就是原型方法,通过prototype属性定义,这个方法不存在于new出来的实例上,只存在于其原型上,实例可以通过原型链访问原型方法,原型方法的好处在于节约内存,把多个实例公用的方法提取出来放到prototype中去,这也就是为什么要定义原型这个概念
所以可以再对比下静态方法和原型方法的区别加深理解
- 请写一个函数实现instanceof的功能
function instanceOf(obj,Cons){ var protoType = Cons.prototype var proto = obj.__proto__ while(proto !== null){ if(proto === protoType){ return true } proto = proto.__proto__ } return false}复制代码
这道题好几个公司都问了,提问方式是你是否知道instanceof的原理,这道题很重要,因为它包含了原型和原型链的知识,面试官不会主动说明这是原型或原型链。首先instanceof是一个关键词,用法a instanceof A
,左操作数是一个对象,右操作数是一个构造函数,意思就是判断a是否是A的实例,那么怎么判断呢,就是通过在左操作数a的原型链上一步一步往上早,然后依次比对a的__proto__和右操作数的prototype是否相等,如果相等则a是A的实例,直到搜索原型链末端null,此时返回false。所以这道题就是考察原型和原型链的知识点,另外最好用Object.getPrototypeOf
获取原型,而不是用__proto__
- 请写一个函数function getLongestPath(root),该函数的参数是html节点,返回值是dom树从根节点开始到叶节点的最长路径,用数组表示,数组中每一个元素是dom节点的标签名
function getLongestPath(root){ if(!root) return []; var tempList = []; for(var i=0;itempList.length){ tempList = ret.slice(); } } tempList.push(root.nodeName.toLowerCase()); return temoList}复制代码
这就是一个递归求多叉树深度的问题,leetcode上有求二叉树深度的问题,这个就是一个变种,且要求出最长路径,原理是一样的,用一个数组保存每个节点到其叶节点的最长路径,然后for循环遍历其所有子节点,不断更新最大长度,注意不要用childeNodes而是要用children,因为前者包含文本和注释节点,不在考虑范围内,另外最好用nodeName代替tagName,因为tagName只适用于元素节点
- 请解释什么是类数组,并举例说明
var arrayLikeObj = { '1':'a', '2':'b', '3':'c', length:3}复制代码
这就是类数组,首先它是一个对象而不是数组,然后拥有length属性,且其他的key都是非负整数的字符串,熟知的arguments对象就是类数组,然后document.getElementsByTagName返回值也是类数组
类数组转化为数组可以用Array.from
或者Array.prototype.slice.call
- 请在js中实现函数重载
function overload(){ var args = arguments var strategy = { '0':function methodA(){}, '1':function methodB(){}, '2':function methodC(){} } return strategy[args.length](...args)}复制代码
其实就是一个策略模式的应用,通过判断arguments这个对象的长度,也就是参数的个数,然后根据不同个数执行对应的方法而已
- 请在数组的原型上实现push方法
Array.prototype.push = function(){ for(var i=0;i
这里注意push可以有多个参数,所以要for循环遍历arguments对象,然后this就是调用push时的数组,直接给length那个位置赋值即可扩充数组,注意length会自增,不用再写this.length++
- 请用2种方式实现一个函数Log,该函数在执行奇数次时打印1,偶数次时打印2,不能用全局变量 也就是要达到如下目的
function Log(){...}Log() //1Log() //2Log() //1Log() //2复制代码
答案就是使用闭包和静态属性而已,先说闭包,首先题目中说不能使用全局变量,第一反应就是闭包,如下
var Log = (function(){ var cnt = 1 return function(){ if(cnt === 1){ console.log(1) cnt++ }else{ console.log(2) cnt-- } }})()复制代码
这就是闭包加自执行函数,Log函数就是里面return的那个function,这个返回的函数引用这外部函数的cnt变量,因此构成闭包结构,cnt没有被回收且cnt是局部变量,下面说下静态方法实现
function Log(){ if(Log.cnt === 1){ console.log(1) cnt++ }else{ console.log2) cnt-- }}Log.cnt = 1;复制代码
这就是static属性,这个cnt属性是加在Log本身上的,不是全局变量,且可以在Log内部访问到,es6的class要使用static关键字定义静态方法
- 请解释Object.create的作用并模拟实现一个该方法 这个方法就是用来创建对象的,函数签名如下
Object.create(proto, [propertiesObject])复制代码
第一个参数是原型对象,第二个参数是一个对象,对象的key是要添加到返回值对象的属性,value是descriptor对象,用法如下
var proto = { a:1}var o = Object.create(proto,{ p: { value: 42, writable: true, enumerable: true, configurable: true} })console.log(o)复制代码
第一个参数proto是生成的对象的原型对象,即o.__proto__ === proto
,然后第二个参数的属性都加在o对象本身上而不是原型上,这个方法可以实现继承,如下
// Shape - 父类(superclass)function Shape() { this.x = 0; this.y = 0;}// 父类的方法Shape.prototype.move = function(x, y) { this.x += x; this.y += y; console.info('Shape moved.');};// Rectangle - 子类(subclass)function Rectangle() { Shape.call(this); // call super constructor.}// 子类续承父类Rectangle.prototype = Object.create(Shape.prototype);Rectangle.prototype.constructor = Rectangle;var rect = new Rectangle();复制代码
下面模拟实现Object.create
function ObjCreate(proto){ var f = function(){} f.prototype = proto return new f()}复制代码
其实很简单,就是显示指明f的原型为proto并返回f的实例对象
- 最后一题,也是很容易忽略的一点,请说明下面的继承方法有什么问题
function Parent() {};function Son() {}Son.prototype = Object.create(Parent.prototype);复制代码
有2个问题,第一个是这仅仅是基于原型链的继承,Parent构造函数内的东西没有继承下来,需要在Son的构造函数内添加Parent.call(this)
,第二点在于没有写如下的代码
Son.prototype.constructor = Son复制代码
这有可能导致问题出现,考虑如下代码
function Parent() {};function CreatedConstructor() {}CreatedConstructor.prototype = Object.create(Parent.prototype);CreatedConstructor.prototype.create = function create() { return new this.constructor();}new CreatedConstructor().create().create(); // error undefined is not a function since constructor === Parent复制代码
这里的CreatedConstructor的原型上有一个create方法,先看return new this.construtor()
这句话,这种用法非常少见,首先要了解constructor是啥,constructor就是一个属性,在构造函数的原型上存在,且指向构造函数本身,constructor就是一个构造函数,所以可以像这么来调用constructor()
,然后它必须要通过this来访问,最后new操作符说明生成了一个对象,这个对象就是CreatedConstructor的实例,也就是说create方法创建了实例本身,就和new CreateConstrutor()
的返回值是一样的
CreatedConstructor.prototype = Object.create(Parent.prototype)
这句话进行原型继承后,CreateConstructor这个构造函数的原型的construtor已经被改写为Parent的constructor,那么我们来看最后一句话 new CreatedConstructor().create().create()复制代码
先看第一个create,这就是new了一个对象并调用其create方法,由于create方法返回实例本身,因此它上面理应还有create方法可以调用,但是此时报错,为什么,就是因为CreateConstructor这个构造函数的原型的construtor已经被改写为Parent的constructor,所以返回的是Parent的实例,Parent原型上根本没有create方法,所以报错。因此我们需要加上如下代码就能够保证CreateConstructor的原型上的构造函数是CreateConstructor本身
CreateConstructor.prototype.constructor = CreateConstructor复制代码
像上面这种题目如果出在面试中应该大部分人都答不出来吧,毕竟比较细节