STAY HUNGRY , STAY FOOLISH.

求知若饥,虚心若愚。

       浏览:

JavaScript高级(二)

5.继承

继承可以分成:构造函数的继承和非构造函数的继承。

5.1 构造函数的继承

构造函数的继承,可以用以下五种方法实现。

1)使用call/apply方法

1
2
3
4
5
6
7
8
9
10
11
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属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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)利用空对象作为中介

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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'

将代码进行封装,代码如下:

1
2
3
4
5
6
7
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)拷贝继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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

1
2
3
4
5
6
var chinese={
nation:'中国'
};
var doctor=new Object(chinese);
doctor.carrer='医生';
doctor.nation;//'中国'

总结:

先在父对象的基础上生成子对象,然后再加上子对象本身的属性,这样也能实现继承。

2)浅拷贝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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)深拷贝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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;//["成都", "南京", "上海"]

将代码进行封装,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
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)回调函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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)事件监听

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
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)发布/订阅

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
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对象。
Deffered对象

Deffered对象

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.高级用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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的设计模式,请大家尽情期待。