5.继承
继承可以分成:构造函数的继承和非构造函数的继承。
5.1 构造函数的继承
构造函数的继承,可以用以下五种方法实现。
1)使用call/apply方法
function People(name, age) {
this.name = name
this.age = age
}
function Student(name, age, grade) {
People.apply(this, arguments)
//People.call(this,name,age);
this.grade = grade
}
var ww = new Student('ww', '22', '100分')
ww.name //'ww'
总结:
a. 用call/apply方法能改变this的指向,上面的代码将People对象指向了Student对象。 b. apply使用方法是apply(this,[arg0,arg1,...]); c. call使用方法是call(this,arg0,arg1,...)。
2)使用prototype属性
function Animals() {
this.category = 'animals'
}
function People() {
this.name = 'ww'
this.say = function () {
console.log('My name is ' + this.name)
}
}
People.prototype.constructor == People //true
People.prototype = new Animals()
People.prototype.constructor == People //false
People.prototype.constructor == Animals.prototype.constructor //true
People.prototype.constructor == Animals //true
People.prototype.constructor = People
People.prototype.constructor == People //true
var p = new People()
p.category //'animals'
p.constructor == People.prototype.constructor //true
总结:
a.任何一个prototype对象都有一个constructor属性,指向它的构造函数。 b.每一个实例也有一个constructor属性,默认调用prototype对象的constructor属性。
3)直接继承prototype
function Animals() {}
Animals.prototype.category = 'animals'
function People() {
this.name = 'ww'
this.say = function () {
console.log('My name is ' + this.name)
}
}
People.prototype = Animals.prototype
Animals.prototype.constructor == Animals //true
People.prototype.constructor = People
Animals.prototype.constructor == Animals //false
var p = new People()
p.category //'animals'
总结:
与方法一相比,这样做的优点:效率比较高(不用执行和建立Animal的实例了),比较省内存;缺点:People.prototype和Animal.prototype现在指向了同一个对象People。
4)利用空对象作为中介
function Animals() {}
Animals.prototype.category = 'animals'
function People() {
this.name = 'ww'
this.say = function () {
console.log('My name is ' + this.name)
}
}
function Empty() {}
Empty.prototype = Animals.prototype
People.prototype = new Empty()
Animals.prototype.constructor == Animals //true
People.prototype.constructor = People
Animals.prototype.constructor == Animals //true
var p = new People()
p.category //'animals'
将代码进行封装,代码如下:
function extend(child, parent) {
var Empty = function () {}
Empty.prototype = parent.prototype
child.prototype = new Empty()
child.prototype.constructor = child
child.uber = parent.prototype
}
总结:
a. 这个extend函数,就是YUI库如何实现继承的方法。 b. 为子对象设一个uber属性,这个属性直接指向父对象的prototype属性。相当于在子对象上打开一条通道,可以直接调用父对象的方法。
5)拷贝继承
function Animals() {}
Animals.prototype.category = 'animals'
function People() {
this.name = 'ww'
this.say = function () {
console.log('My name is ' + this.name)
}
}
function extend(child, parent) {
var p = parent.prototype
var c = child.prototype
for (var i in p) {
c[i] = p[i]
}
c.uber = p
}
extend(People, Animals)
var p = new People()
p.category //'animals'
5.2 非构造函数的继承
非构造函数的继承,可以用以下三种方法实现。
1)使用Object
var chinese = {
nation: '中国',
}
var doctor = new Object(chinese)
doctor.carrer = '医生'
doctor.nation //'中国'
总结:
先在父对象的基础上生成子对象,然后再加上子对象本身的属性,这样也能实现继承。
2)浅拷贝
function copy(parent) {
var child = {}
for (var i in parent) {
child[i] = parent[i]
}
child.uber = parent
return child
}
var chinese = {
nation: '中国',
birth: ['成都', '南京', '上海'],
}
var doctor = copy(chinese)
doctor.carrer = '医生'
doctor.nation //'中国'
doctor.birth.push('北京')
doctor.birth //["成都", "南京", "上海", "北京"]
chinese.birth //["成都", "南京", "上海", "北京"]
总结:
a.把父对象的属性,全部拷贝给子对象,也能实现继承。 b.如果父对象的属性等于数组或另一个对象,那么实际上,子对象获得的只是一个内存地址,而不是真正地拷贝,因此存在父对象被篡改的可能。
3)深拷贝
function deepCopy(parent, child) {
var child = child || {}
for (var i in parent) {
if (typeof parent[i] === 'object') {
child[i] = parent[i].constructor === Array ? [] : {}
deepCopy(parent[i], child[i])
} else {
child[i] = parent[i]
}
}
return child
}
var chinese = {
nation: '中国',
birth: ['成都', '南京', '上海'],
}
var doctor = deepCopy(chinese)
doctor.carrer = '医生'
doctor.nation //'中国'
doctor.birth.push('北京')
doctor.birth //["成都", "南京", "上海", "北京"]
chinese.birth //["成都", "南京", "上海"]
将代码进行封装,代码如下:
function extend(parent, child) {
var child = child || {}
for (var i in parent) {
if (typeof parent[i] === 'object') {
child[i] = parent[i].constructor === Array ? [] : {}
extend(parent[i], child[i])
} else {
child[i] = parent[i]
}
}
return child
}
总结:
a. 所谓"深拷贝",就是能够实现真正意义上的数组和对象的拷贝。它的实现并不难,只要递归调用"浅拷贝"就行了。 b. jQuery库使用的就是这种继承方法。
大总结:
继承的方式如此多,那究竟该用哪个呢? 简单讲实现继承最好的方式有三个:
1.使用apply/call方法;2.使用“第三者”将父对象的prototype赋值给子对象的prototype;3.递归拷贝父对象的属性和方法到子对象。
6.异步
将异步之前,先理解下JavaScript的运行机制。
当JavaScript代码被浏览器解析时,会生成一个主线程,同步任务将从上到下依次执行。可是如果遇到耗时很长的异步任务时(如调接口,定时器等),会把一个个异步任务存放在"任务队列"里,执行完同步任务后,"任务队列"通知主线程某个异步任务可以执行了,该任务才会进入主线程执行。只要主线程空了,主线程就会去读取"任务队列","任务队列"反复通知,主线程反复执行,当异步任务都完成后主线程结束,这就是JavaScript的运行机制。
主线程从"任务队列"中读取异步任务,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件轮询)。
所以异步是为了解决JavaScript执行环境是单线程问题。
异步编程有以下几种方法:
1.回调函数2.事件监听3.发布/订阅 4.Promises/A规范的对象
1)回调函数
function test() {
console.log('1')
setTimeout(function () {
console.log('2')
}, 10)
console.log('3')
}
test() //1,3,2
function test2(callback) {
console.log('1')
setTimeout(function () {
console.log('2')
callback()
}, 10)
}
test2(function () {
console.log('3')
}) //1,2,3
总结:
回调函数的优点:简单,缺点:高度耦合,每个任务只能指定一个回调函数。
2)事件监听
var ww = {
events: new Array(),
AddNewEvent: function (eventName) {
var newEvent = {
eventName: eventName,
eventHandles: new Array(),
}
this.events.push(newEvent)
return newEvent
},
GetEventByName: function (eventName) {
for (var i = 0; i < this.events.length; i++) {
if (this.events[i].eventName == eventName) {
return this.events[i]
}
}
return null
},
AddEventhandler: function (eventName, handler) {
var myevent = this.GetEventByName(eventName)
if (myevent == null) {
myevent = this.AddNewEvent(eventName)
}
myevent.eventHandles.push(handler)
},
RaiseEvent: function (eventName, params) {
if (!eventName) {
console.log('no eventName')
return
}
var myevent = this.GetEventByName(eventName)
if (myevent != null) {
for (var i = 0; i < myevent.eventHandles.length; i++) {
if (myevent.eventHandles[i] != null) {
if (params != null) {
myevent.eventHandles[i].call(this, params)
} else {
myevent.eventHandles[i].call(this)
}
}
}
} else {
console.log('no event')
}
},
}
function test2() {
console.log('1')
setTimeout(function () {
console.log('2')
ww.RaiseEvent('whoNum', { num: '3' })
}, 10)
ww.AddEventhandler('whoNum', function (obj) {
console.log(obj.num)
})
}
test2() //1,2,3
总结:
事件监听的优点:去耦合,缺点:运行流程会变得很不清晰。
3)发布/订阅
var PubSub = {}
;(function (p) {
var topics = {},
lastUid = -1
p.publish = function (topic, data) {
if (!topics.hasOwnProperty(topic)) {
return false
}
var subscribers = topics[topic]
for (var i = 0, j = subscribers.length; i < j; i++) {
subscribers[i].func(topic, data)
}
return true
}
p.subscribe = function (topic, func) {
if (!topics.hasOwnProperty(topic)) {
topics[topic] = []
}
var token = (++lastUid).toString()
topics[topic].push({ token: token, func: func })
return token
}
p.unsubscribe = function (token) {
for (var m in topics) {
if (topics.hasOwnProperty(m)) {
for (var i = 0, len = topics[m].length; i < len; i++) {
if (topics[m][i].token === token) {
topics[m].splice(i, 1)
return token
}
}
}
}
}
})(PubSub)
function test2() {
console.log('1')
setTimeout(function () {
console.log('2')
PubSub.publish('whoNum')
}, 10)
PubSub.subscribe('whoNum', function () {
console.log('3')
})
}
test2() //1,2,3
总结:
事件监听的优点:去耦合,缺点:运行流程会变得很不清晰。
4.Promises/A规范的对象
Promises/A是由CommonJS组织制定的异步模式编程规范,有不少库已根据该规范及后来经改进的Promises/A+规范提供了实现,如Q, Bluebird, when, rsvp.js, mmDeferred, jQuery.Deffered()等。
下面重点讲下jQuery.Deffered对象。



总结:
1.$.Deferred()生成deferred对象
()里可以是匿名函数,默认参数为生成的deferred对象
2.promise() 无法操作resolve()、reject()
3.done()相当于success
fail()相当于error
4.resolve() 将未完成变成已完成 然后触发done
reject() 将未完成变成已失败 然后触发fail
5.then() 把done和fail一起写 then(successFunc,failFunc)
6.$.when() 为多个函数指定同一个回调函数
使用Deferred为了解决异步问题,具体执行步骤:
loadData方法、loadHtml方法执行完成后执行C方法
loadData <-- getCategoryList <-- ajax返回的数据
设置接口的值:dtd.resolve(data) ajax
获取接口的值:loadData().done(function(data){使用data})
return $.Deferred()层层调用里面的值
7.高级用法
var data=0;//0,null,undefined,'',false
var channelList=data || 'default'; //'default'
var ww={
add:function(){
console.log('add');
}
};
ww&&ww.add(); //'add'
function($){})(jQuery); //声明带JQuery对象参数的函数
$.fn.extend({}); //给jQuery实例添加方法 $("XXX").method()
$.extend({});//给jQuery类添加方法 $.method()
if(new RegExp('^\\d+$').test(2)){//(/^\d+$/.test(2))
console.log('type is intager');
}
总结:
JavaScript高级用法不是说写法多么的高级,而是在项目中能起到的作用有多高。真正学好JavaScript并不是件容易的事情,还有很长的路需要走。
历经近一周的撰写,终于要和《JavaScript高级》说再见了,不是说后期不会再写JavaScript,而是现在自己认为该总结的都差不多了,如果有遗漏的地方,我后期还会继续讲解。
这篇文章和上篇文章不适合初学者学习和使用,但是随着知识的积累,相信你们也能随便看懂。 下一篇讲JavaScript的设计模式,请大家尽情期待。