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
下面我们来测试一下:
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
如果这么写就返回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
为 false
,configurable
为 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;
};
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)
最后:
总结来说,面试成功=基础知识+项目经验+表达技巧+运气。我们无法控制运气,但是我们可以在别的地方花更多时间,每个环节都提前做好准备。
面试一方面是为了找到工作,升职加薪,另一方面也是对于自我能力的考察。能够面试成功不仅仅是来自面试前的临时抱佛脚,更重要的是在平时学习和工作中不断积累和坚持,把每个知识点、每一次项目开发、每次遇到的难点知识,做好积累,实践和总结。
这个函数的作用非常的简单。就是加强一下构造函数,所谓的加强构造函数就是给构造函数或者其原型上添加一些函数。
而_defineProperties
就是多个_defineProperty
(感觉是废话,不过的确如此)。默认 enumerable
为 false
,configurable
为 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;
};
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-m8hbNO2P-1713637615343)]
[外链图片转存中…(img-RPY3WaEq-1713637615344)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!
[外链图片转存中…(img-QAh6Tgyc-1713637615344)]
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)
[外链图片转存中…(img-UHqxXOMR-1713637615344)]
最后:
总结来说,面试成功=基础知识+项目经验+表达技巧+运气。我们无法控制运气,但是我们可以在别的地方花更多时间,每个环节都提前做好准备。
面试一方面是为了找到工作,升职加薪,另一方面也是对于自我能力的考察。能够面试成功不仅仅是来自面试前的临时抱佛脚,更重要的是在平时学习和工作中不断积累和坚持,把每个知识点、每一次项目开发、每次遇到的难点知识,做好积累,实践和总结。