打算以一名【合格】前端工程師的自檢清單爲綱整理本身的知識體系,因爲是整理,風格偏簡潔,勘誤、疑惑、意見建議可前往小弟博客交流,後續的整理也會在博客及時更新,博客地址github.com/logan70/Blo…。html
原型前端
JavaScript中,一個對象從被建立開始就和另外一個對象關聯,從另外一個對象上繼承其屬性,這個另外一個對象
就是原型。vue
獲取原型的方法jquery
Object.getPrototypeOf(obj)
來獲取obj
的原型。new
出來的(字面量對象也能夠這麼理解),也能夠經過訪問對應構造函數的prototype
屬性來獲取其原型。const obj = {}
expect(Object.getPrototypeOf(obj) === Object.prototype).toBe(true)
複製代碼
原型鏈git
當訪問一個對象的屬性時,先在對象的自己找,找不到就去對象的原型上找,若是仍是找不到,就去對象的原型(原型也是對象,也有它本身的原型)的原型上找,如此繼續,直到找到爲止,或者查找到最頂層的原型對象中也沒有找到,就結束查找,返回undefined
。這條由對象及其原型組成的鏈就叫作原型鏈。github
原型鏈頂層編程
普通對象能夠理解爲Object
構造函數建立的,即普通對象的原型都指向Object.prototype
,Object.prototype
也是個對象,可是其原型比較特殊,爲null
,是原型鏈的頂層,切記!!!設計模式
expect(Object.getPrototypeOf({})).toBe(Object.prototype)
expect(Object.getPrototypeOf(Object.prototype)).toBe(null)
複製代碼
構造函數的原型babel
函數,包括構造函數均可理解爲由構造函數Function
建立,Function
自己也不例外。前端工程師
const getProto = Object.getPrototypeOf
const FuncProto = Function.prototype
expect(getProto(Function)).toBe(FuncProto)
expect(getProto(Object)).toBe(FuncProto)
expect(getProto(Number)).toBe(FuncProto)
expect(getProto(Symbol)).toBe(FuncProto)
expect(getProto(Array)).toBe(FuncProto)
複製代碼
instanceof
用於檢測右側構造函數的原型是否存在於左側對象的原型鏈上。
Symbol.hasInstance
ES6
新增的內置Symbol,用做對象方法標識符,該方法用於檢測任意對象是否爲擁有該方法對象的實例。instanceof
操做符優先使用該Symbol
對應的屬性。
這樣一來instanceof
右側並不是必須爲函數,對象也能夠的。示例代碼以下:
const MyArray = {
[Symbol.hasInstance](obj) {
return Array.isArray(obj)
}
}
expect([] instanceof MyArray).toBe(true)
複製代碼
const isObj = obj => ((typeof obj === 'object') || (typeof obj === 'function')) && obj !== null
function myInstanceOf(instance, Ctor) {
if (!isObj(Ctor)) // 右側必須爲對象
throw new TypeError('Right-hand side of 'instanceof' is not an object')
const instOfHandler = Ctor[Symbol.hasInstance]
// 右側有[Symbol.hasInstance]方法,則返回其執行結果
if (typeof instOfHandler === 'function') return instOfHandler(instance)
// 右側無[Symbol.hasInstance]方法且不是函數的,返回false
if (typeof Ctor !== 'function') return false
// 左側實例不是對象類型,返回false
if (!isObj(instance)) return false
// 右側函數必須有原型
const rightP = Ctor.prototype
if (!isObj(rightP))
throw new TypeError(`Function has non-object prototype '${String(rightP)}' in instanceof check`)
// 在實例原型連上查找是否有Ctor原型,有則返回true
// 知道找到原型鏈頂級尚未,則返回false
while (instance !== null) {
instance = Object.getPrototypeOf(instance)
if (instance === null) return false
if (instance === rightP) return true
}
}
複製代碼
標準出處 -> ECMAScript#instanceof
InstanceofOperator ( V, target )
OrdinaryHasInstance ( C, O )
面向對象編程(Object Oriented Programming),簡稱OOP,是一種程序設計思想。OOP把對象做爲程序的基本單元,一個對象包含了數據和操做數據的函數。
面向對象編程的三大基本特性:封裝,繼承,多態。
將一段邏輯/概念抽象出來作到「相對獨立」。封裝的概念並非OOP獨有的,而是長久以來一直被普遍採用的方法。主要目的總結爲兩點:
JavaScript中典型的封裝就是模塊化,實現方法有閉包、ES Module、AMD、CMD、CommonJS等。
多態的概念也不是OOP獨有的。所謂多態就是同一操做做用於不一樣的對象上面,能夠產生不一樣的解釋和不一樣的執行結果。
多態的目的就是用對象的多態性消除條件分支語句,提高程序的可拓展性。
JavaScript是一門弱類型多態語言,具備與生俱來的多態性。
兩大概念:
JavaScript中沒有類的概念,使用構造函數做爲對象模板,經過原型鏈來實現繼承(ES6 中的Class
只是語法糖)。
原型及原型鏈相關知識詳見深刻JavaScript系列(六):原型與原型鏈
將父類實例賦值給子類原型。缺點以下:
function SupClass() {...}
function SubClass() {...}
SubClass.prototype = new SupClass()
複製代碼
子類構造函數中執行父類構造函數。缺點以下:
function SupClass() {...}
function SubClass() {
SupClass.call(this, arguments)
}
複製代碼
類式繼承+構造函數繼承。缺點以下:
function SupClass() {...}
function SubClass() {
SupClass.call(this, arguments)
}
SubClass.prototype = new SupClass()
複製代碼
對類式繼承的封裝,功能相似Object.create
,缺點以下:
function createObj(o) {
function F() {}
F.prototype = o
return new F()
}
複製代碼
拓展原型式繼承
建立的對象並返回。
function createObj(o) {
const obj = Object.create(o)
obj.name = 'Logan'
return obj
}
複製代碼
寄生式繼承+構造函數式繼承。
function inherit(child, parent) {
const p = Object.create(parent.prototype)
child.prototype = p
p.constructor = child
return child
}
function SupClass() {...}
function SubClass() {
SupClass.call(this)
}
SubClass = inherit(SubClass, SupClass)
複製代碼
var jQuery = function(selector, context) {
return new jQuery.fn.init(selector, context)
}
jQuery.fn = jQuery.prototype = {
constructor: jQuery,
... // 各類原型方法
}
jQuery.fn.init = function(selector, context, root) { ... }
jQuery.fn.init.prototype = jQuery.fn // 校訂實例的原型
複製代碼
function Vue(options) {
this._init(options)
}
// initMixin
Vue.prototype._init = function (options) { ... }
// stateMixin
Object.defineProperty(Vue.prototype, '$data', {...})
Object.defineProperty(Vue.prototype, '$props', {...})
Vue.prototype.$set = function() {...}
Vue.prototype.$delete = function() {...}
Vue.prototype.$watch = function() {...}
// eventMixin
Vue.prototype.$on = function() {...}
Vue.prototype.$once = function() {...}
Vue.prototype.$off = function() {...}
Vue.prototype.$emit = function() {...}
// lifecycleMixin
Vue.prototype._update = function() {...}
Vue.prototype.$forceUpdate = function() {...}
Vue.prototype.$destory = function() {...}
// renderMixin
Vue.prototype.$nextTick = function() {...}
Vue.prototype._render = function() {...}
複製代碼
new
一個對象的詳細過程new
function newOperator(Ctor, ...args) {
if (typeof Ctor !== 'function') {
throw new TypeError('First argument is not a constructor')
}
// 1. 建立一個全新對象,並將該對象原型指向構造函數的原型對象
const obj = Object.create(Ctor.prototype)
// 2. 將構造函數調用的this指向這個新對象,並執行構造函數;
const result = Ctor.apply(obj, args)
// 3. 若是構造函數執行結果爲對象類型,則返回執行結果,不然返回建立的新對象
return (result instanceof Object) ? result : obj
}
複製代碼
ES6 中的類
Class
,僅僅只是基於現有的原型繼承的一種語法糖,咱們一塊兒來看一下Class
的底層實現原理。
Class
;Class
可定義實例屬性方法和靜態屬性方法;Class
的實例可繼承父Class
上的實例屬性方法、子Class
可繼承父Class
上的靜態屬性方法。Class
實現思路:使用instanceof
操做符檢測實例是否爲指定類的實例。
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError('Cannot call a class as a function')
}
}
複製代碼
實現思路
function _createClass(Constructor, protoProps = [], staticProps = []) {
// 在構造函數的原型上定義實例屬性方法
_defineProperties(Constructor.prototype, protoProps)
// 在構造函數自己定義靜態屬性方法
_defineProperties(Constructor, staticProps)
}
// 實現公用的批量給對象添加屬性方法的方法
function _defineProperties(target, props) {
props.forEach(prop => {
Object.defineProperty(target, prop.key, prop)
})
}
複製代碼
實現思路:借用原型鏈繼承實現。
function _inherits(subClass, superClass) {
// 子類實例繼承父類的實例屬性方法
subClass.prototype = Object.create(superClass.prototype)
// 修正constructor屬性
subClass.prototype.constructor = subClass
// 子類繼承父類的靜態屬性方法
Object.setPrototypeOf(subClass, superClass)
}
複製代碼
瞭解了Class
的底層實現要素,咱們就來將Class
模擬編譯爲使用原型繼承實現的代碼。
class Person {
constructor(options) {
this.name = options.name
this.age = options.age
}
eat() {
return 'eating'
}
static isPerson(instance) {
return instance instanceof Person
}
}
class Student extends Person {
constructor(options) {
super(options)
this.grade = options.grade
}
study() {
return 'studying'
}
static isStudent(instance) {
return instance instanceof Student
}
}
複製代碼
var Person = (function() {
function Person(options) {
// 確保使用new調用
_classCallCheck(this, Person)
this.name = options.name
this.age = options.age
}
_createClass(
Person,
// 實例屬性方法
[{
key: 'eat',
value: function eat() {
return 'eating'
}
}],
// 靜態屬性方法
[{
key: 'isPerson',
value: function isPerson(instance) {
return instance instanceof Person
}
}]
)
return Person
})();
var Student = (function (_Person) {
// 繼承父類實例屬性方法和靜態屬性方法
_inherits(Student, _Person)
function Student(options) {
// 確保使用new調用
_classCallCheck(this, Student)
// 執行父類構造函數
_Person.call(this, options)
this.grade = options.grade
}
_createClass(Student,
// 實例屬性方法
[{
key: 'study',
value: function study() {
return 'studying'
}
}],
// 靜態屬性方法
[{
key: 'isStudent',
value: function isStudent(instance) {
return instance instanceof Student
}
}]
)
return Student
})(Person);
複製代碼
const person = new Person({ name: 'Logan', age: 18 })
const student = new Student({ name: 'Logan', age: 18, grade: 9 })
expect(person.eat()).toBe('eating')
expect(student.eat()).toBe('eating') // 繼承實例方法
expect(student.study()).toBe('studying')
expect(Student.isStudent(student)).toBe(true)
expect(Person.isPerson(person)).toBe(true)
expect(Student.isStudent(person)).toBe(false)
expect(Student.isPerson(student)).toBe(true) // 繼承靜態方法
複製代碼
公有(public)和私有(private)字段聲明目前在JavaScript標準委員會TC39的 試驗性功能 (第3階段),下面進行模擬實現。
使用
class ClassWithStaticField {
static staticField1 = 'static field' // 設定初始值
static staticField2 // 不設定初始值
}
複製代碼
polyfill
function ClassWithStaticField() {}
// @babel/plugin-proposal-class-properties 中使用Object.defineProperty實現,此處簡便起見直接賦值
ClassWithStaticField.staticField1 = 'static field' // 設定初始值
ClassWithStaticField.staticField2 = undefined // 不設定初始值
複製代碼
使用
class ClassWithInstanceField {
instanceField1 = 'instance field' // 設定初始值
instanceField2 // 不設定初始值
}
複製代碼
polyfill
function ClassWithInstanceField() {
// @babel/plugin-proposal-class-properties 中使用Object.defineProperty實現,此處簡便起見直接賦值
this.instanceField1 = 'instance field' // 設定初始值
this.instanceField2 = undefined // 不設定初始值
}
複製代碼
靜態私有字段只能在靜態方法內訪問,且只能經過類的屬性進行訪問,不能經過this進行訪問。
使用
class ClassWithPrivateStaticField {
static #PRIVATE_STATIC_FIELD
static publicStaticMethod() {
ClassWithPrivateStaticField.#PRIVATE_STATIC_FIELD = 42
return ClassWithPrivateStaticField.#PRIVATE_STATIC_FIELD
}
}
複製代碼
polyfill
經過閉包實現靜態私有字段
var ClassWithPrivateStaticField = (function() {
var _PRIVATE_STATIC_FIELD
function ClassWithPrivateStaticField() {}
ClassWithPrivateStaticField.publicStaticMethod = function() {
_PRIVATE_STATIC_FIELD = 42
return _PRIVATE_STATIC_FIELD
}
return ClassWithPrivateStaticField
})();
複製代碼
使用
class ClassWithPrivateField {
#privateField;
constructor() {
this.#privateField = 42;
console.log(this.$privateField)
}
}
複製代碼
polyfill
經過WeakMap結合實例自己爲key實現
var ClassWithPrivateField = (function() {
var _privateField = new WeakMap()
function ClassWithPrivateField() {
_privateField.set(this, undefined)
_privateField.set(this, 42)
console.log(_privateField.get(this))
}
})();
複製代碼