最新“美团+字节+腾讯”三面问题,JS原型相关知识点,最新金九银十前端面试合集

console.log(SubClass instanceof SuperClass)//false

从我们之前介绍的 instanceof 的原理我们知道,第三个 console 如果这么写就返回 true 了console.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 !!rightSymbol.hasInstance;

} 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(感觉是废话,不过的确如此)。默认 enumerable 为 falseconfigurable 为 true

其实如上就是 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

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

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

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

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

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024c (备注前端)
img

专业技能

一般来说,面试官会根据你的简历内容去提问,但是技术基础还有需要自己去准备分类,形成自己的知识体系的。简单列一下我自己遇到的一些题

  • HTML+CSS

  • JavaScript

  • 前端框架

  • 前端性能优化

  • 前端监控

  • 模块化+项目构建

  • 代码管理

  • 信息安全

  • 网络协议

  • 浏览器

  • 算法与数据结构

  • 团队管理

最近得空把之前遇到的面试题做了一个整理,包括我本人自己去面试遇到的,还有其他人员去面试遇到的,还有网上刷到的,我都统一的整理了一下,希望对大家有用。

其中包含HTML、CSS、JavaScript、服务端与网络、Vue、浏览器等等

由于文章篇幅有限,仅展示部分内容

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

g-64L1uI63-1712545861130)]
[外链图片转存中…(img-6Z5GpAEh-1712545861130)]
[外链图片转存中…(img-rjhGCo1R-1712545861130)]
[外链图片转存中…(img-RdGnkG0V-1712545861131)]
[外链图片转存中…(img-qNCaQHgM-1712545861131)]

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

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

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024c (备注前端)
[外链图片转存中…(img-gwy1a3YM-1712545861131)]

专业技能

一般来说,面试官会根据你的简历内容去提问,但是技术基础还有需要自己去准备分类,形成自己的知识体系的。简单列一下我自己遇到的一些题

  • HTML+CSS

  • JavaScript

  • 前端框架

  • 前端性能优化

  • 前端监控

  • 模块化+项目构建

  • 代码管理

  • 信息安全

  • 网络协议

  • 浏览器

  • 算法与数据结构

  • 团队管理

最近得空把之前遇到的面试题做了一个整理,包括我本人自己去面试遇到的,还有其他人员去面试遇到的,还有网上刷到的,我都统一的整理了一下,希望对大家有用。

其中包含HTML、CSS、JavaScript、服务端与网络、Vue、浏览器等等

由于文章篇幅有限,仅展示部分内容

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
[外链图片转存中…(img-5YFGHRgM-1712545861132)]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值