最后
面试一面会问很多基础问题,而这些基础问题基本上在网上搜索,面试题都会很多很多。最好把准备一下常见的面试问题,毕竟面试也相当与一次考试,所以找工作面试的准备千万别偷懒。面试就跟考试一样的,时间长了不复习,现场表现肯定不会太好。表现的不好面试官不可能说,我猜他没发挥好,我录用他吧。
开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】
96道前端面试题:
常用算法面试题:
前端基础面试题:
内容主要包括HTML,CSS,JavaScript,浏览器,性能优化
系列文章均首发于公众号【全栈前端精选】,笔者文章集合详见GitHub 地址:Nealyang/personalBlog。目录和发文顺序皆为暂定
首先我想说,【THE LAST TIME】系列的的内容,向来都是包括但不限于标题的范围。
再回来说原型,老生常谈的问题了。但是着实 现在不少熟练工也貌似没有梳理清楚 Function
和 Object
、prototype
和__proto__
的关系,本文将从原型到继承到 es6 语法糖的实现来介绍系统性的介绍 JavaScript 继承。如果你能够回答上来以下问题,那么这位看官,基本这篇不用再花时间阅读了~
-
为什么
typeof
判断null
是Object
类型? -
Function
和Object
是什么关系? -
new
关键字具体做了什么?手写实现。 -
prototype
和__proto__
是什么关系?什么情况下相等? -
ES5 实现继承有几种方式,优缺点是啥
-
ES6 如何实现一个类
-
ES6
extends
关键字实现原理是什么
如果对以上问题有那么一些疑惑~那么。。。
THE LAST TIME 系列回顾
目录
–
虽文章较长,但较为基础。大家酌情阅读所需章节。
注意文末有思考题哦~~
-
原型一把梭
-
- 函数对象和普通对象
-
__proto__
-
prototype
-
constructor
-
typeof
&&instanceof
原理浅析 -
typeof
基本用法
-
typeof
原理浅析 -
instanceof
基本用法 -
instanceof
原理浅析 -
ES5 中的继承实现方式
-
new
手写版本一
-
new
手写版本二 -
new
关键字 -
类式继承
-
构造函数继承
-
组合式继承
-
原型式继承
-
寄生式继承
-
寄生组合式继承
-
ES6 类的实现原理
-
_inherits
-
_possibleConstructorReturn
-
基础类
-
添加属性
-
添加方法
-
extend
关键字
原型一把梭
这。。。说是最基础没人反驳吧,说没有用有人反驳吧,说很多人到现在没梳理清楚没人反驳吧!OK~ 为什么文章那么多,你却还没有弄明白?
在概念梳理之前,我们还是放一张老掉牙所谓的经典神图:
-
function Foo
就是一个方法,比如JavaScript 中内置的Array
、String
等 -
function Object
就是一个Object
-
function Function
就是Function
-
以上都是
function
,所以__proto__
都是Function.prototype
-
再次强调,
String、Array、Number、Function、Object
都是function
老铁,如果对这张图已非常清晰,那么可直接跳过此章节
老规矩,我们直接来梳理概念。
函数对象和普通对象
老话说,万物皆对象。而我们都知道在 JavaScript 中,创建对象有好几种方式,比如对象字面量,或者直接通过构造函数 new
一个对象出来:
暂且我们先不管上面的代码有什么意义。至少,我们能看出,都是对象,却存在着差异性
其实在 JavaScript 中,我们将对象分为函数对象和普通对象。所谓的函数对象,其实就是 JavaScript 的用函数来模拟的类实现。JavaScript 中的 Object 和 Function 就是典型的函数对象。
关于函数对象和普通对象,最直观的感受就是。。。咱直接看代码:
function fun1(){}; const fun2 = function(){}; const fun3 = new Function('name','console.log(name)'); const obj1 = {}; const obj2 = new Object(); const obj3 = new fun1(); const obj4 = new new Function(); console.log(typeof Object);//function console.log(typeof Function);//function console.log(typeof fun1);//function console.log(typeof fun2);//function console.log(typeof fun3);//function console.log(typeof obj1);//object console.log(typeof obj2);//object console.log(typeof obj3);//object console.log(typeof obj4);//object
不知道大家看到上述代码有没有一些疑惑的地方~别着急,我们一点一点梳理。
上述代码中,obj1
,obj2
,obj3
,obj4
都是普通对象,fun1
,fun2
,fun3
都是 Function
的实例,也就是函数对象。
所以可以看出,所有 Function 的实例都是函数对象,其他的均为普通对象,其中包括 Function 实例的实例。
JavaScript 中万物皆对象,而对象皆出自构造(构造函数)。
上图中,你疑惑的点是不是 Function
和 new Function
的关系。其实是这样子的:
Function.__proto__ === Function.prototype//true
__proto__
首先我们需要明确两点:1️⃣
__proto__
和constructor
是对象独有的。2️⃣prototype
属性是函数独有的;
但是在 JavaScript 中,函数也是对象,所以函数也拥有__proto__
和 constructor
属性。
结合上面我们介绍的 Object
和 Function
的关系,看一下代码和关系图
function Person(){…}; let nealyang = new Person();
proto
再梳理上图关系之前,我们再来讲解下__proto__
。
__proto__
的例子,说起来比较复杂,可以说是一个历史问题。
ECMAScript 规范描述 prototype
是一个隐式引用,但之前的一些浏览器,已经私自实现了 __proto__
这个属性,使得可以通过 obj.__proto__
这个显式的属性访问,访问到被定义为隐式属性的 prototype
。
因此,情况是这样的,ECMAScript 规范说 prototype
应当是一个隐式引用:
-
通过
Object.getPrototypeOf(obj)
间接访问指定对象的prototype
对象 -
通过
Object.setPrototypeOf(obj, anotherObj)
间接设置指定对象的prototype
对象 -
部分浏览器提前开了
__proto__
的口子,使得可以通过obj.__proto__
直接访问原型,通过obj.__proto__ = anotherObj
直接设置原型 -
ECMAScript 2015 规范只好向事实低头,将
__proto__
属性纳入了规范的一部分
从浏览器的打印结果我们可以看出,上图对象 a
存在一个__proto__
属性。而事实上,他只是开发者工具方便开发者查看原型的故意渲染出来的一个虚拟节点。虽然我们可以查看,但实则并不存在该对象上。
__proto__
属性既不能被 for in
遍历出来,也不能被 Object.keys(obj)
查找出来。
访问对象的 obj.__proto__
属性,默认走的是 Object.prototype
对象上 __proto__
属性的 get/set 方法。
Object.defineProperty(Object.prototype,'__proto__',{ get(){ console.log('get') } }); ({}).__proto__; console.log((new Object()).__proto__);
关于更多__proto__
更深入的介绍,可以参看工业聚大佬的《深入理解 JavaScript 原型》一文。
这里我们需要知道的是,__proto__
是对象所独有的,并且__proto__
是一个对象指向另一个对象,也就是他的原型对象。我们也可以理解为父类对象。它的作用就是当你在访问一个对象属性的时候,如果该对象内部不存在这个属性,那么就回去它的__proto__
属性所指向的对象(父类对象)上查找,如果父类对象依旧不存在这个属性,那么就回去其父类的__proto__
属性所指向的父类的父类上去查找。以此类推,知道找到 null
。而这个查找的过程,也就构成了我们常说的原型链。
prototype
object that provides shared properties for other objects
在规范里,prototype 被定义为:给其它对象提供共享属性的对象。prototype
自己也是对象,只是被用以承担某个职能罢了.
所有对象,都可以作为另一个对象的 prototype
来用。
修改__proto__
的关系图,我们添加了 prototype
,prototype
是函数所独有的。**它的作用就是包含可以给特定类型的所有实例提供共享的属性和方法。它的含义就是函数的远行对象,**也就是这个函数所创建的实例的远行对象,正如上图:nealyang.__proto__ === Person.prototype
。任何函数在创建的时候,都会默认给该函数添加 prototype
属性.
constructor
constructor
属性也是对象所独有的,它是一个对象指向一个函数,这个函数就是该对象的构造函数。
注意,每一个对象都有其对应的构造函数,本身或者继承而来。单从constructor
这个属性来讲,只有prototype
对象才有。每个函数在创建的时候,JavaScript 会同时创建一个该函数对应的prototype
对象,而函数创建的对象.__proto__ === 该函数.prototype
,该函数.prototype.constructor===该函数本身
,故通过函数创建的对象即使自己没有constructor
属性,它也能通过__proto__
找到对应的constructor
,所以任何对象最终都可以找到其对应的构造函数。
唯一特殊的可能就是我开篇抛出来的一个问题。JavaScript 原型的老祖宗:Function
。它是它自己的构造函数。所以Function.prototype === Function.__proto
。
为了直观了解,我们在上面的图中,继续添加上constructor
:
其中 constructor
属性,虚线表示继承而来的 constructor 属性。
__proto__
介绍的原型链,我们在图中直观的标出来的话就是如下这个样子
typeof && instanceof 原理
问什么好端端的说原型、说继承会扯到类型判断的原理上来呢。毕竟原理上有一丝的联系,往往面试也是由浅入深、顺藤摸瓜的拧出整个知识面。所以这里我们也简单说一下吧。
typeof
MDN 文档点击这里:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/typeof
基本用法
typeof
的用法相比大家都比较熟悉,一般被用于来判断一个变量的类型。我们可以使用 typeof
来判断number
、undefined
、symbol
、string
、function
、boolean
、object
这七种数据类型。但是遗憾的是,typeof
在判断 object
类型时候,有些许的尴尬。它并不能明确的告诉你,该 object
属于哪一种 object
。
let s = new String('abc'); typeof s === 'object'// true typeof null;//"object"
原理浅析
要想弄明白为什么 typeof
判断 null
为 object
,其实需要从js 底层如何存储变量类型来说起。虽然说,这是 JavaScript 设计的一个 bug。
在 JavaScript 最初的实现中,JavaScript 中的值是由一个表示类型的标签和实际数据值表示的。对象的类型标签是 0。由于 null
代表的是空指针(大多数平台下值为 0x00),因此,null
的类型标签是 0,typeof null
也因此返回 "object"
。曾有一个 ECMAScript 的修复提案(通过选择性加入的方式),但被拒绝了。该提案会导致 typeof null === 'null'
。
js 在底层存储变量的时候,会在变量的机器码的低位1-3位存储其类型信息:
-
1:整数
-
110:布尔
-
100:字符串
-
010:浮点数
-
000:对象
但是,对于 undefined
和 null
来说,这两个值的信息存储是有点特殊的:
-
null
:所有机器码均为0 -
undefined
:用 −2^30 整数来表示
所以在用 typeof
来判断变量类型的时候,我们需要注意,最好是用 typeof
来判断基本数据类型(包括symbol
),避免对 null
的判断。
typeof
只是咱在讨论原型带出的instanceof
的附加讨论区
instanceof
object instanceof constructor
instanceof
和 typeof
非常的类似。instanceof
运算符用来检测 constructor.prototype
是否存在于参数 object
的原型链上。与 typeof
方法不同的是,instanceof
方法要求开发者明确地确认对象为某特定类型。
基本用法
// 定义构造函数 function C(){} function D(){} var o = new C(); o instanceof C; // true,因为 Object.getPrototypeOf(o) === C.prototype o instanceof D; // false,因为 D.prototype 不在 o 的原型链上 o instanceof Object; // true,因为 Object.prototype.isPrototypeOf(o) 返回 true C.prototype instanceof Object // true,同上 C.prototype = {}; var o2 = new C(); o2 instanceof C; // true o instanceof C; // false,C.prototype 指向了一个空对象,这个空对象不在 o 的原型链上. D.prototype = new C(); // 继承 var o3 = new D(); o3 instanceof D; // true o3 instanceof C; // true 因为 C.prototype 现在在 o3 的原型链上
如上,是 instanceof
的基本用法,它可以判断一个实例是否是其父类型或者祖先类型的实例。
console.log(Object instanceof Object);//true console.log(Function instanceof Function);//true console.log(Number instanceof Number);//false console.log(String instanceof String);//false console.log(Function instanceof Object);//true console.log(Foo instanceof Function);//true console.log(Foo instanceof Foo);//false
为什么 Object
和 Function
instanceof
自己等于 true
,而其他类 instanceof
自己却又不等于 true
呢?如何解释?
要想从根本上了解 instanceof
的奥秘,需要从两个方面着手:1,语言规范中是如何定义这个运算符的。2,JavaScript 原型继承机制。
原理浅析
经过上述的分析,想必大家对这种经典神图已经不那么陌生了吧,那咱就对着这张图来聊聊 instanceof
这里,我直接将规范定义翻译为 JavaScript 代码如下:
function instance_of(L, R) {//L 表示左表达式,R 表示右表达式 var O = R.prototype;// 取 R 的显示原型 L = L.__proto__;// 取 L 的隐式原型 while (true) { if (L === null) return false; if (O === L)// 这里重点:当 O 严格等于 L 时,返回 true return true; L = L.__proto__; } }
所以如上原理,加上上文解释的原型相关知识,我们再来解析下为什么Object
和 Function
instanceof
自己等于 true
。
Object instanceof Object
// 为了方便表述,首先区分左侧表达式和右侧表达式 ObjectL = Object, ObjectR = Object; // 下面根据规范逐步推演 O = ObjectR.prototype = Object.prototype L = ObjectL.__proto__ = Function.prototype // 第一次判断 O != L // 循环查找 L 是否还有 __proto__ L = Function.prototype.__proto__ = Object.prototype // 第二次判断 O == L // 返回 true
- Function instanceof Function
// 为了方便表述,首先区分左侧表达式和右侧表达式 FunctionL = Function, FunctionR = Function; // 下面根据规范逐步推演 O = FunctionR.prototype = Function.prototype L = FunctionL.__proto__ = Function.prototype // 第一次判断 O == L // 返回 true
Foo instanceof Foo
// 为了方便表述,首先区分左侧表达式和右侧表达式 FooL = Foo, FooR = Foo; // 下面根据规范逐步推演 O = FooR.prototype = Foo.prototype L = FooL.__proto__ = Function.prototype // 第一次判断 O != L // 循环再次查找 L 是否还有 __proto__ L = Function.prototype.__proto__ = Object.prototype // 第二次判断 O != L // 再次循环查找 L 是否还有 __proto__ L = Object.prototype.__proto__ = null // 第三次判断 L == null // 返回 false
ES5 中的继承实现方式
在继承实现上,工业聚大大在他的原型文章中,将原型继承分为两大类,显式继承和隐式继承。感兴趣的可以点击文末参考链接查看。
但是本文还是希望能够基于“通俗”的方式来讲解几种常见的继承方式和优缺点。大家可多多对比查看,其实原理都是一样,名词也只是所谓的代称而已。
关于继承的文章,很多书本和博客中都有很详细的讲解。以下几种继承方式,均总结与《JavaScript 设计模式》一书。也是笔者三年前写的一篇文章了。
new 关键字
在讲解继承之前呢,我觉得 new
这个东西很有必要介绍下~
一个例子看下new
关键字都干了啥
function Person(name,age){ this.name = name; this.age = age; this.sex = 'male'; } Person.prototype.isHandsome = true; Person.prototype.sayName = function(){ console.log(`Hello , my name is ${this.name}`); } let handsomeBoy = new Person('Nealyang',25); console.log(handsomeBoy.name) // Nealyang console.log(handsomeBoy.sex) // male console.log(handsomeBoy.isHandsome) // true handsomeBoy.sayName(); // Hello , my name is Nealyang
从上面的例子我们可以看到:
-
访问到
Person
构造函数里的属性 -
访问到
Person.prototype
中的属性
new 手写版本一
function objectFactory() { const obj = new Object(),//从Object.prototype上克隆一个对象 Constructor = [].shift.call(arguments);//取得外部传入的构造器 const F=function(){}; F.prototype= Constructor.prototype; obj=new F();//指向正确的原型 Constructor.apply(obj, arguments);//借用外部传入的构造器给obj设置属性 return obj;//返回 obj };
-
用
new Object()
的方式新建了一个对象 obj -
取出第一个参数,就是我们要传入的构造函数。此外因为 shift 会修改原数组,所以
arguments
会被去除第一个参数 -
将 obj 的原型指向构造函数,这样 obj 就可以访问到构造函数原型中的属性
-
使用
apply
,改变构造函数this
的指向到新建的对象,这样 obj 就可以访问到构造函数中的属性 -
返回 obj
下面我们来测试一下:
最后
四轮技术面+一轮hr面结束,学习到了不少,面试也是一个学习检测自己的过程,面试前大概复习了 一周的时间,把以前的代码看了一下,字节跳动比较注重算法,面试前刷了下leetcode和剑指offer, 也刷了些在牛客网上的面经。大概就说这些了,写代码去了~
祝大家都能收获大厂offer~
篇幅有限,仅展示部分内容
取出第一个参数,就是我们要传入的构造函数。此外因为 shift 会修改原数组,所以 arguments
会被去除第一个参数
-
将 obj 的原型指向构造函数,这样 obj 就可以访问到构造函数原型中的属性
-
使用
apply
,改变构造函数this
的指向到新建的对象,这样 obj 就可以访问到构造函数中的属性 -
返回 obj
下面我们来测试一下:
最后
四轮技术面+一轮hr面结束,学习到了不少,面试也是一个学习检测自己的过程,面试前大概复习了 一周的时间,把以前的代码看了一下,字节跳动比较注重算法,面试前刷了下leetcode和剑指offer, 也刷了些在牛客网上的面经。大概就说这些了,写代码去了~
祝大家都能收获大厂offer~
篇幅有限,仅展示部分内容