所有JS原型相关知识点,2024年最新华为前端面试题

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Web前端全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上前端开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip1024c (备注前端)
img

正文

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 来判断numberundefinedsymbolstringfunctionbooleanobject 这七种数据类型。但是遗憾的是,typeof 在判断 object 类型时候,有些许的尴尬。它并不能明确的告诉你,该 object 属于哪一种 object

let s = new String('abc'); typeof s === 'object'// true typeof null;//"object"

原理浅析

要想弄明白为什么 typeof 判断 nullobject,其实需要从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:对象

但是,对于 undefinednull 来说,这两个值的信息存储是有点特殊的:

  • null:所有机器码均为0

  • undefined:用 −2^30 整数来表示

所以在用 typeof 来判断变量类型的时候,我们需要注意,最好是用 typeof 来判断基本数据类型(包括symbol),避免对 null 的判断。

typeof 只是咱在讨论原型带出的 instanceof 的附加讨论区

instanceof

object instanceof constructor

instanceoftypeof 非常的类似。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

为什么 ObjectFunction 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__; } }

所以如上原理,加上上文解释的原型相关知识,我们再来解析下为什么ObjectFunction 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

下面我们来测试一下:

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}`); } function objectFactory() { let obj = new Object(),//从Object.prototype上克隆一个对象 Constructor = [].shift.call(arguments);//取得外部传入的构造器 console.log({Constructor}) const F=function(){}; F.prototype= Constructor.prototype; obj=new F();//指向正确的原型 Constructor.apply(obj, arguments);//借用外部传入的构造器给obj设置属性 return obj;//返回 obj }; let handsomeBoy = objectFactory(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

注意上面我们没有直接修改 obj 的__proto__隐式挂载。

new 手写版本二

考虑构造函数又返回值的情况:

  • 如果构造函数返回一个对象,那么我们也返回这个对象

  • 如上否则,就返回默认值

function objectFactory() { var obj = new Object(),//从Object.prototype上克隆一个对象 Constructor = [].shift.call(arguments);//取得外部传入的构造器 var F=function(){}; F.prototype= Constructor.prototype; obj=new F();//指向正确的原型 var ret = Constructor.apply(obj, arguments);//借用外部传入的构造器给obj设置属性 return typeof ret === 'object' ? ret : obj;//确保构造器总是返回一个对象 };

关于 call、apply、bind、this 等用法和原理讲解:【THE LAST TIME】this:call、apply、bind

类式继承

function SuperClass() { this.superValue = true; } SuperClass.prototype.getSuperValue = function() { return this.superValue; } function SubClass() { this.subValue = false; } SubClass.prototype = new SuperClass(); SubClass.prototype.getSubValue = function() { return this.subValue; } var instance = new SubClass(); console.log(instance instanceof SuperClass)//true console.log(instance instanceof SubClass)//true console.log(SubClass instanceof SuperClass)//false

从我们之前介绍的 instanceof 的原理我们知道,第三个 console 如果这么写就返回 trueconsole.log(SubClass.prototype instanceof SuperClass)

虽然实现起来清晰简洁,但是这种继承方式有两个缺点:

  • 由于子类通过其原型prototype对父类实例化,继承了父类,所以说父类中如果共有属性是引用类型,就会在子类中被所有的实例所共享,因此一个子类的实例更改子类原型从父类构造函数中继承的共有属性就会直接影响到其他的子类

  • 由于子类实现的继承是靠其原型prototype对父类进行实例化实现的,因此在创建父类的时候,是无法向父类传递参数的。因而在实例化父类的时候也无法对父类构造函数内的属性进行初始化

构造函数继承

function SuperClass(id) { this.books = ['js','css']; this.id = id; } SuperClass.prototype.showBooks = function() { console.log(this.books); } function SubClass(id) { //继承父类 SuperClass.call(this,id); } //创建第一个子类实例 var instance1 = new SubClass(10); //创建第二个子类实例 var instance2 = new SubClass(11); instance1.books.push('html'); console.log(instance1) console.log(instance2) instance1.showBooks();//TypeError

SuperClass.call(this,id)当然就是构造函数继承的核心语句了.由于父类中给this绑定属性,因此子类自然也就继承父类的共有属性。由于这种类型的继承没有涉及到原型prototype,所以父类的原型方法自然不会被子类继承,而如果想被子类继承,就必须放到构造函数中,这样创建出来的每一个实例都会单独的拥有一份而不能共用,这样就违背了代码复用的原则,所以综合上述两种,我们提出了组合式继承方法

组合式继承

function SuperClass(name) { this.name = name; this.books = ['Js','CSS']; } SuperClass.prototype.getBooks = function() { console.log(this.books); } function SubClass(name,time) { SuperClass.call(this,name); this.time = time; } SubClass.prototype = new SuperClass(); SubClass.prototype.getTime = function() { console.log(this.time); }

如上,我们就解决了之前说到的一些问题,但是是不是从代码看,还是有些不爽呢?至少这个SuperClass的构造函数执行了两遍就感觉非常的不妥.

原型式继承

function inheritObject(o) { //声明一个过渡对象 function F() { } //过渡对象的原型继承父对象 F.prototype = o; //返回过渡对象的实例,该对象的原型继承了父对象 return new F(); }

原型式继承大致的实现方式如上,是不是想到了我们new关键字模拟的实现?

其实这种方式和类式继承非常的相似,他只是对类式继承的一个封装,其中的过渡对象就相当于类式继承的子类,只不过在原型继承中作为一个普通的过渡对象存在,目的是为了创建要返回的新的实例对象。

var book = { name:'js book', likeBook:['css Book','html book'] } var newBook = inheritObject(book); newBook.name = 'ajax book'; newBook.likeBook.push('react book'); var otherBook = inheritObject(book); otherBook.name = 'canvas book'; otherBook.likeBook.push('node book'); console.log(newBook,otherBook);

如上代码我们可以看出,原型式继承和类式继承一个样子,对于引用类型的变量,还是存在子类实例共享的情况。

所以,我们还有下面的寄生式继

寄生式继承

var book = { name:'js book', likeBook:['html book','css book'] } function createBook(obj) { //通过原型方式创建新的对象 var o = new inheritObject(obj); // 拓展新对象 o.getName = function(name) { console.log(name) } // 返回拓展后的新对象 return o; }

其实寄生式继承就是对原型继承的拓展,一个二次封装的过程,这样新创建的对象不仅仅有父类的属性和方法,还新增了别的属性和方法。

寄生组合式继承

回到之前的组合式继承,那时候我们将类式继承和构造函数继承组合使用,但是存在的问题就是子类不是父类的实例,而子类的原型是父类的实例,所以才有了寄生组合式继承

而寄生组合式继承是寄生式继承和构造函数继承的组合。但是这里寄生式继承有些特殊,这里他处理不是对象,而是类的原型。

function inheritObject(o) { //声明一个过渡对象 function F() { } //过渡对象的原型继承父对象 F.prototype = o; //返回过渡对象的实例,该对象的原型继承了父对象 return new F(); } function inheritPrototype(subClass,superClass) { // 复制一份父类的原型副本到变量中 var p = inheritObject(superClass.prototype); // 修正因为重写子类的原型导致子类的constructor属性被修改 p.constructor = subClass; // 设置子类原型 subClass.prototype = p; }

组合式继承中,通过构造函数继承的属性和方法都是没有问题的,所以这里我们主要探究通过寄生式继承重新继承父类的原型。

我们需要继承的仅仅是父类的原型,不用去调用父类的构造函数。换句话说,在构造函数继承中,我们已经调用了父类的构造函数。因此我们需要的就是父类的原型对象的一个副本,而这个副本我们可以通过原型继承拿到,但是这么直接赋值给子类会有问题,因为对父类原型对象复制得到的复制对象p中的constructor属性指向的不是subClass子类对象,因此在寄生式继承中要对复制对象p做一次增强,修复起constructor属性指向性不正确的问题,最后将得到的复制对象p赋值给子类原型,这样子类的原型就继承了父类的原型并且没有执行父类的构造函数。

function SuperClass(name) { this.name = name; this.books=['js book','css book']; } SuperClass.prototype.getName = function() { console.log(this.name); } function SubClass(name,time) { SuperClass.call(this,name); this.time = time; } inheritPrototype(SubClass,SuperClass); SubClass.prototype.getTime = function() { console.log(this.time); } var instance1 = new SubClass('React','2017/11/11') var instance2 = new SubClass('Js','2018/22/33'); instance1.books.push('test book'); console.log(instance1.books,instance2.books); instance2.getName(); instance2.getTime();

这种方式继承其实如上图所示,其中最大的改变就是子类原型中的处理,被赋予父类原型中的一个引用,这是一个对象,因此有一点你需要注意,就是子类在想添加原型方法必须通过prototype.来添加,否则直接赋予对象就会覆盖从父类原型继承的对象了.

ES6 类的实现原理


关于 ES6 中的 class 的一些基本用法和介绍,限于篇幅,本文就不做介绍了。该章节,我们主要通过 babel的 REPL来查看分析 es6 中各个语法糖包括继承的一些实现方式。

基础类

我们就会按照这个类,来回摩擦。然后再来分析编译后的代码。

"use strict"; function _instanceof(left, right) { if ( right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance] ) { return !!right[Symbol.hasInstance](left); } else { return left instanceof right; } } function _classCallCheck(instance, Constructor) { if (!_instanceof(instance, Constructor)) { throw new TypeError("Cannot call a class as a function"); } } var Person = function Person(name) { _classCallCheck(this, Person); this.name = name; };

_instanceof就是来判断实例关系的的。上述代码就比较简单了,_classCallCheck的作用就是检查 Person 这个类,是否是通过new 关键字调用的。毕竟被编译成 ES5 以后,function 可以直接调用,但是如果直接调用的话,this 就指向 window 对象,就会Throw Error了.

添加属性

"use strict"; function _instanceof(left, right) {...} function _classCallCheck(instance, Constructor) {...} function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } var Person = function Person(name) { _classCallCheck(this, Person); _defineProperty(this, "shili", '实例属性'); this.name = name; }; _defineProperty(Person, "jingtai", ' 静态属性');

其实就是讲属性赋值给谁的问题。如果是实例属性,直接赋值到 this 上,如果是静态属性,则赋值类上。_defineProperty也就是来判断下是否属性名重复而已。

添加方法

"use strict"; function _instanceof(left, right) {...} function _classCallCheck(instance, Constructor) {...} function _defineProperty(obj, key, value) {...} function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } var Person = /*#__PURE__*/ function () { function Person(name) { _classCallCheck(this, Person); _defineProperty(this, "shili", '实例属性'); this.name = name; } _createClass(Person, [{ key: "sayName", value: function sayName() { return this.name; } }, { key: "name", get: function get() { return 'Nealyang'; }, set: function set(newName) { console.log('new name is :' + newName); } }], [{ key: "eat", value: function eat() { return 'eat food'; } }]); return Person; }(); _defineProperty(Person, "jingtai", ' 静态属性');

看起来代码量还不少,其实就是一个_createClass函数和_defineProperties函数而已。

首先看_createClass这个函数的三个参数,第一个是构造函数,第二个是需要添加到原型上的函数数组,第三个是添加到类本身的函数数组。其实这个函数的作用非常的简单。就是加强一下构造函数,所谓的加强构造函数就是给构造函数或者其原型上添加一些函数。

_defineProperties就是多个_defineProperty(感觉是废话,不过的确如此)。默认 enumerablefalseconfigurabletrue

其实如上就是 es6 class 的实现原理。

extend 关键字

"use strict"; function _instanceof(left, right) {...} function _classCallCheck(instance, Constructor) {...} var Parent = function Parent(name) {...}; function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); } function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); } function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); } function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } var Child = /*#__PURE__*/ function (_Parent) { _inherits(Child, _Parent); function Child(name, age) { var _this; _classCallCheck(this, Child); _this = _possibleConstructorReturn(this, _getPrototypeOf(Child).call(this, name)); // 调用父类的 constructor(name) _this.age = age; return _this; } return Child; }(Parent); var child1 = new Child('全栈前端精选', '0.3'); console.log(child1);

删去类相关的代码生成,剩下的就是继承的语法糖剖析了。其中super 关键字表示父类的构造函数,相当于 ES5 的 Parent.call(this),然后再根据我们上文说到的继承方式,有没有感觉该集成的实现跟我们说的寄生组合式继承非常的相似呢?

在 ES6 class 中,子类必须在 constructor 方法中调用 super 方法,否则新建实例时会报错。这是因为子类没有自己的 this 对象,而是继承父类的 this 对象,然后对其进行加工。如果不调用 super 方法,子类就得不到 this 对象。

也正是因为这个原因,在子类的构造函数中,只有调用 super 之后,才可以使用 this 关键字,否则会报错。

关于 ES6 中原型链示意图可以参照如下示意图:

图片来自冴羽的博客

关于ES6 中的 extend 关键字,上述代码我们完全可以根据执行来看。其实重点代码无非就两行:

_inherits(Child, _Parent); _this = _possibleConstructorReturn(this, _getPrototypeOf(Child).call(this, name));

我们分别来分析下具体的实现:

_inherits

代码比较简单,都是上文提到的内容,就是建立 Child 和 Parent 的原型链关系。代码解释已备注在代码内

function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) {//subClass 类型判断 throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: {//Object.create 第二个参数是给subClass.prototype添加了 constructor 属性 value: subClass, writable: true, configurable: true//注意这里enumerable没有指名,默认是 false,也就是说constructor为不可枚举的。 } }); if (superClass) _setPrototypeOf(subClass, superClass); } function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }

_possibleConstructorReturn

_this = _possibleConstructorReturn(this, _getPrototypeOf(Child).call(this, name));

根据上图我们整理的 es6 原型图可知:

Child.prototype === Parent

所以上面的代码我们可以翻译为:

_this = _possibleConstructorReturn(this, Parent.call(this, name));

然后我们再一层一层拨源码的实现

function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); } function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }

上述代码,self其实就是 Child 的 IIFE返回的 function new 调用的 this,打印出来结果如下:

这里可能对Parent.call(this,name)有些疑惑,没关系,我们可以在 Chrome 下调试下。

可以看到,当我们 Parent 的构造函数这么写

class Parent { constructor(name) { this.name = name; } }

那么最终,传递给_possibleConstructorReturn函数的第二参数 call就是一个 undefined。所以在_possibleConstructorReturn函数里面会对 call进行判断,返回正确的 this 指向:Child

所以整体代码的目的就是根据 Parent 构造函数的返回值类型确定子类构造函数 this 的初始值 _this

最后

【THE LAST TIME】系列关于 JavaScript 基础的文章目前更新三篇,我们最后再来一道经典的面试题吧!

function Foo() { getName = function() { alert(1); }; return this; } Foo.getName = function() { alert(2); }; Foo.prototype.getName = function() { alert(3); }; var getName = function() { alert(4); }; function getName() { alert(5); } //请写出以下输出结果: Foo.getName(); getName(); Foo().getName(); getName(); new Foo.getName(); new Foo().getName(); new new Foo().getName();

老铁,评论区留下你的思考吧~

参考文献


总结一下

面试前要精心做好准备,简历上写的知识点和原理都需要准备好,项目上多想想难点和亮点,这是面试时能和别人不一样的地方。

还有就是表现出自己的谦虚好学,以及对于未来持续进阶的规划,企业招人更偏爱稳定的人。

万事开头难,但是程序员这一条路坚持几年后发展空间还是非常大的,一切重在坚持。

为了帮助大家更好更高效的准备面试,特别整理了《前端工程师面试手册》电子稿文件。

前端面试题汇总

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024c (备注前端)
img

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
,评论区留下你的思考吧~

参考文献


总结一下

面试前要精心做好准备,简历上写的知识点和原理都需要准备好,项目上多想想难点和亮点,这是面试时能和别人不一样的地方。

还有就是表现出自己的谦虚好学,以及对于未来持续进阶的规划,企业招人更偏爱稳定的人。

万事开头难,但是程序员这一条路坚持几年后发展空间还是非常大的,一切重在坚持。

为了帮助大家更好更高效的准备面试,特别整理了《前端工程师面试手册》电子稿文件。

前端面试题汇总

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024c (备注前端)
[外链图片转存中…(img-H9kmzsK8-1713609786239)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 16
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值