博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
听说你精通原生JavaScript,来试试这份题目吧
阅读量:6995 次
发布时间:2019-06-27

本文共 6504 字,大约阅读时间需要 21 分钟。

前言

一年经验侥幸拿到字节跳动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)}复制代码

解释: returnin运算符判定了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;i
tempList.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复制代码

像上面这种题目如果出在面试中应该大部分人都答不出来吧,毕竟比较细节

转载地址:http://rrbvl.baihongyu.com/

你可能感兴趣的文章
《Flash建站技术》系列6-LoadVars数据提交与表单处理
查看>>
Service Mesh:什么是Sidecar模式
查看>>
python string
查看>>
纠结:决策有依据、局限和方法,也有后果需要承担
查看>>
小米纪录片《一团火》上映,这团火烧到你了吗?
查看>>
一篇好的BUG报告是如何炼成的
查看>>
要做好性能测试,该掌握些什么?
查看>>
今天配置java + selenium 3.0出了很多问题,记录如下
查看>>
xen虚拟化里常用的一些配置
查看>>
在用vi编辑文件时遇到“Terminal too wide”的提示
查看>>
RHEL6和RHEL7的变化
查看>>
VMware 虚拟机设置U盘启动(老毛桃 PE)
查看>>
程序员注意了!这样的公司千万不要去!
查看>>
文件服务器--samba和ftp的搭建
查看>>
我的友情链接
查看>>
ubuntu 14.10桌面登陆失败
查看>>
java并发编程,ThreadLocal源码解析
查看>>
textbox只能输入数字
查看>>
Summer School实验二
查看>>
sed命令的使用及案例
查看>>