晚更了一天,?
1. ES5和ES6的继承有什么区别?
- ES5
- 原型链继承
原型链继承的基本思想是让一个引用类型继承另一个引用类型的属性和方法。
基本模式如下:
function SuperType () {
this.property = true
}
SuperType.propertype.getSuperValue = function () {
return this.property
}
function SubType () {
this.subproperty = false
}
// 继承了SuperType
SubType.propertype = new SuperType()
SubType.propertype.getSubValue = function () {
return this.subproperty
}
var foo = new SubType()
foo.getSuperValue() // ture
复制代码
这里将SuperType的实例对象赋值给了SubType的原型对象,从而是实现继承。实现本质是重写原型对象,代之已一个新类型的实例。
复制代码
- 构造函数继承
基本思想就是子类型构造函数的内部调用超类型构造函数
function SuperType () {
this.colors = ["red", "blue", "green"]
}
function SubType () {
// 继承了SuperType
SuperType.call(this)
}
var foo = new SubType()
foo.colors.push("black")
console.log(foo.colors) // ["red", "blue", "green", "black"]
var bar = new SuperType()
console.log(bar.colors) // ["red", "blue", "green"]
bar.colors.push("yellow")
console.log(bar.colors) // ["red", "blue", "green", "yellow"]
console.log(foo.colors) // ["red", "blue", "green", "black"]
复制代码
通过使用call和apply方法在将来新创建的对象上执行构造函数。
- 组合继承
组合继承指的是将原型链和借用构造函数的技术组合到一起,思路是使用原型链实现对原型属性和方法的继承,通过借用构造函数实现对实例属性的继承。
function SuperType(name) {
this.name = name
this.colors = ["red", "blue", "green"]
}
SuperType.prototype.sayName = function () {
console.log(this.name)
}
function SubType (name, age) {
// 继承属性,这里SubType的实例就有了name属性
SuperType.call(this, name)
this.age = age
}
// 继承方法
SubType.prototype = new SuperType()
SubType.prototype.constructor = SubType
SubType.prototype.sayAge = function () {
console.log(this.age)
}
var foo = new SubType('zz', 19)
foo.colors.push("balck")
console.log(foo.colors) // ["red", "blue", "green", "black"]
foo.sayName() // 'zz'
foo.sayAge() // 19
var bar = new SubType('xx', 20)
console.log(bar.colors) // ["red", "blue", "green"]
bar.sayName() // 'xx'
bar.sayAge() // 20
复制代码
- 原型式继承
借助原型可以基于已有的对象创建新的对象
var person = {
name: 'father',
friends: ['a', 'b']
}
var son = Object.create(person)
son.name = 'son'
son.friends.push('c')
console.log(son.friends) // ['a', 'b', 'c']
console.log(son.name) // 'son'
console.log(person.friends) // ['a', 'b', 'c']
console.log(person.name) // 'father'
var daughter = Object.create(person)
daughter.name = 'daughter'
daughter.friends.push('d')
console.log(daughter.friends) // ['a', 'b', 'c', 'd']
console.log(daughter.name) // 'daughter'
console.log(son.friends) // ['a', 'b', 'c', 'd']
console.log(son.name) // 'son'
console.log(person.friends) // ['a', 'b', 'c', 'd']
console.log(person.name) // 'father'
复制代码
利用Object.create(obj),进行一次对象的浅拷贝,对于基本类型的值可以改变,对于引用类型的值还是共享的。
- 寄生式继承
思路:创建一个仅用于封装继承过程的函数,该函数内部以某种方式来增强对象,最后返回对象。
function createObj (obj) {
var clone = Object.create(obj)
// 增强对象
clone.say = function () {
console.log('得到增强')
}
return clone
}
var person = {
name: 'father',
friends: ['a', 'b']
}
var foo = createObj(person)
foo.say() // '得到增强'
复制代码
- 寄生组合式继承(业内提倡)
思路:借用构造函数来继承属性,通过原型链的混成形式来继承方法。本质就是使用寄生式继承来继承超类的原型,再讲结果指定给子类型的原型。
function inheritPrototype (SuperType, SubType) {
var prototype = Object.create(SuperType.prototype) // 创建对象
prototype.constructor = SubType // 增强对象
prototype.do = function () {
console.log('已经得到增强')
}
SubType.prototype = prototype // 指定对象
}
function SuperType(name) {
this.name = name
this.colors = ["red", "blue", "green"]
}
SuperType.prototype.sayName = function () {
console.log(this.name)
}
function SubType(name, age) {
// 继承属性
SuperType.call(this, name)
this.age = age
}
inheritPrototype(SuperType, SubType)
SubType.prototype.sayAge = function () {
console.log(this.age)
}
var baz = new SubType('jc', 22)
baz.sayAge() // 22
baz.sayName() // ‘jc’
baz.do() // '已经得到增强'
复制代码
ES5的继承机制简单来说就是:实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this))
- ES6 class
class Point {
constructor(x) {
this.x = 1;
this.p = 2;
}
print() {
return this.x;
}
}
Point.prototype.z = '4'
class ColorPoint extends Point {
constructor(x) {
this.color = color; // ReferenceError
super(x, y);
this.x = x; // 正确
}
m() {
super.print();
}
}
复制代码
ES6继承是通过class丶extends 关键字来实现继承 Point是父类,ColorPoint 是子类 通过 class 新建子类 extends继承父类的方式实现继承,方式比ES5简单的多。
-
constructor
constructor 方法是类的构造函数,是一个默认方法,通过 new 命令创建对象实例时,自动调用该方法。一个类必须有 constructor 方法,如果没有显式定义,一个默认的 consructor 方法会被默认添加。一般 constructor 方法返回实例对象 this ,但是也可以指定 constructor 方法返回一个全新的对象,让返回的实例对象不是该类的实例。
class Points {
constructor(x) {
this.x = 1;
this.p = 2;
}
print() {
return this.x;
}
statc getname(){
return new.target.name;
}
}
等同于:
function Points(x) {
this.x = x;
this.p = 2;
}
Points.prototype.print = function() {
return '(' + this.x +')';
}
复制代码
也就是说constructor就代表在父类上加属性,而在class对象加方法属性 等于在原型上的加。而这些属性方法 只有通过new出的实例 或者是extends 继承出来的实例才可以获取到 所以我们可以得到。
new Points().__proto__.print() //可以调用到Points的print方法
new Points().x = 1 //可以调用到constructor 里面的 this.x=1
复制代码
-
super
super既可以当做函数使用,也可以当做对象使用两种使用的时候完全不一样, 函数用时 : 在 constructor 中必须调用 super 方法,因为子类没有自己的 this 对象,而是继承父类的 this 对象,然后对其进行加工,而 super 就代表了父类的构造函数。super 虽然代表了父类 A 的构造函数,但是返回的是子类 B 的实例,即 super 内部的 this 指的是 B,因此 super() 在这里相当于
A.prototype.constructor.call(this, props)
复制代码
当做对象使用 : 在普通方法中,指向父类的原型对象;在静态方法中,指向父类。 所以在子类的方法中super.print();指向的是父类原型上的方法。 但是因为super的两种用法,所以es6规定在使用必须要明确使用方式,像单独console.log(super) 就会报错。
-
static
顾名思义是静态方法的意思,类相当于实例的原型, 所有在类中定义的方法, 都会被实例继承。 如果在一个方法前, 加上static关键字, 就表示该方法不会被实例继承, 而是直接通过类来调用, 这就称为“ 静态方法”。静态方法调用直接在类上进行,而在类的实例上不可被调用。静态方法通常用于创建 实用/工具 函数。
-
new.target
new.target属性一般用在构造函数之中,返回new命令作用于的那个构造函数。如果构造函数不是通过new命令或Reflect.construct()调用的,new.target会返回undefined,因此这个属性可以用来确定构造函数是怎么调用的。
class Rectangle {
constructor(length, width) {
console.log(new.target === Rectangle);
this.length = length;
this.width = width;
}
}
var obj = new Rectangle(3, 4); // 输出 true
复制代码
-
ES6的继承机制总结
先创建父类实例this 通过class丶extends丶super关键字定义子类,并改变this指向,super本身是指向父类的构造函数但做函数调用后返回的是子类的实例,实际上做了父类.prototype.constructor.call(this),做对象调用时指向父类.prototype,从而实现继承。
2. setTimeout、Promise、Async/Await 的区别
看一道面试题
// 今日头条面试题
async function async1() {
console.log('async1 start')
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2')
}
console.log('script start')
setTimeout(function () {
console.log('settimeout')
})
async1()
new Promise(function (resolve) {
console.log('promise1')
resolve()
}).then(function () {
console.log('promise2')
})
console.log('script end')
复制代码
结果:
这题主要考虑的是这三者在事件循环中的区别,事件循环中分为宏任务队列和微任务队列。
- 其中settimeout的回调函数放到宏任务队列里,等到执行栈清空以后执行;
- promise.then里的回调函数会放到相应宏任务的微任务队列里,等宏任务里面的同步代码执行完再执行;
- async函数表示函数里面可能会有异步方法,await后面跟一个表达式,async方法执行时,遇到await会立即执行表达式,然后把表达式后面的代码放到微任务队列里,让出执行栈让同步代码先执行。
- event loop(事件循环) JS主线程不断的循环往复的从任务队列中读取任务,执行任务,这中运行机制称为事件循环。
- Microtasks(微任务)、Macrotasks(宏任务) 微任务和宏任务是异步任务的一种类型,微任务的优先级要高于宏任务,下面是它们所包含的api:
-
microtasks
- process.nextTick
- promise
- Object.observe (废弃)
- MutationObserver
-
macrotasks
- setTimeout
- setImmerdiate
- setInterval
- I/O
- UI 渲染
(关于事件循环,宏任务,微任务以及异步运行机制等,会在后面专门写一篇文章)
3. Vue双向数据绑定
vue 2.x版本还是通过Object.defineProperty()来劫持data的set和get实现的,3.0版本已是通过Proxy实现,这里还是2.x版本原理
通过数据劫持和发布者-订阅者模式实现,通过Object.defineProperty()劫持各个属性的setter和getter,在数据变动时,发布消息给订阅者,触发相应的监听回调
- 需要observe(观察)的数据对象进行递归遍历加上setter和getter
- compile解析模板指令,将模板中的变量换成数据,进行渲染,并对每个指令对应的节点绑定更新函数,添加监听对应数据的订阅者,一旦数据变化,收到消息,执行更新函数,更新视图
- watcher订阅者是连接observe和complie的桥梁,
- 在自身实例化的时候往属性订阅器dep里添加自己,
- 自身拥有update()方法,
- 等属性变动dep.notice()通知时,调用自身update()方法,触发compile里更新函数
- MVVM整合observe,compile和watcher三者,通过observe监听自己model数据的变化,通过compile解析模板指令,最后通过watcher搭起observe和compile的通信桥梁,实现数据变化,视图更新,视图交互变化,数据model变更的双向绑定
实现一下双向绑定
function myVue(options) { // options传入的对象,属性包含data,methods等
this._init(options)
}
// init属性
myVue.prototype._init = function (options) {
this.$options = options
this.$el = document.querySelector(options.el)
this.$data = options.data
this.$methods = options.methods
this._binding = {} //_binding保存着model与view的映射关系
this._observe(this.$data)
this._complie(this.$el);
}
// observe,对data进行处理,重写data的set和get函数
myVue.prototype._observe = function (obj) { // 这里obj指data {number: 0}
var _this = this
Object.keys(obj).forEach(function(key) {
if (obj.hasOwnProperty(key)) {
_this._binding[key] = {
_directives: [] // 指令
}
console.log(_this._binding[key])
var value = obj[key]
if (typeof value == 'object') { // 复杂基本类型进行递归
_this._observe(value)
}
var binding = _this._binding[key]
// 关键,改写set和get
Object.defineProperty(_this.$data, key, {
enumerable: true,
configurable: true,
get: function () {
console.log(`${key}获取${value}`)
return value
},
set: function (newVal) {
console.log(`${key}更新${newVal}`)
if (value != newVal) {
value = newVal
binding._directives.forEach(function(item) {
item.update()
})
}
}
})
}
})
}
// complie 指令,解析模板
myVue.prototype._complie = function (root) {
var _this = this
var nodes = root.children
for (var i=0; i<nodes.length; i++) {
var node = nodes[i]
if (node.children.length) {
this._complie(node)
}
// 用属性名解析对应的指令
if (node.hasAttribute('v-click')) {
node.onclick = (function() {
var attrVal = node.getAttribute('v-click')
return _this.$methods[attrVal].bind(_this.$data)
})()
}
// 双向绑定的实现
if (node.hasAttribute('v-model') && (node.tagName == 'INPUT' || node.tagName == 'TEXTAREA')) {
node.addEventListener('input', (function (key) {
var attrVal = node.getAttribute('v-model')
// 每个指令节点绑定更新函数
_this._binding[attrVal]._directives.push(new Watcher(
'input',
node,
_this,
attrVal,
'value'
))
return function () {
_this.$data[attrVal] = nodes[key].value
}
})(i))
}
if (node.hasAttribute('v-bind')) {
var attrVal = node.getAttribute('v-bind')
_this._binding[attrVal]._directives.push(new Watcher(
'text',
node,
_this,
attrVal,
'innerHTML'
))
}
}
}
// 指令类 watcher,用来绑定更新函数,实现对DOM元素的更新
function Watcher (name, el, vm, exp, attr) {
this.name = name //指令名称
this.el = el //指令对应的DOM元素
this.vm = vm //指令所属myVue实例
this.exp = exp //指令对应的值
this.attr = attr //绑定的属性值
this.update() // 更新
}
// 自身更新函数
Watcher.prototype.update = function () {
this.el[this.attr] = this.vm.$data[this.exp]
}
window.onload = function () {
var app = new myVue({
el:'#app',
data: {
number: 0
},
methods: {
add: function() {
this.number ++;
},
}
})
}
复制代码