Fork me on GitHub

JavaScript学习笔记(二)

基本介绍

  • Javascript基于对象的三大特征和C++,Java面向对象的三大特征一样,即封装(encapsulation)继承(inheritance)多态(polymorphism)
  • JavaScript中,函数内部可以直接读取全局变量;另一方面,若不使用闭包,在函数外部无法读取函数内的局部变量
  • 要实现从外部读取局部变量,可使用闭包,即在函数的内部再定义一个函数。(闭包就是能读取其他函数内部变量的函数,一个定义在函数内部的函数)
  • JavaScript中本身有提供一些可以直接使用的内部类。

Javascript基于对象的三大特征

  • 封装(encapsulation)
  • 继承(inheritance)
  • 多态(polymorphism)
封装
  • 封装 就是把 抽象出来的数据对数据的操作 封装在一起,数据被保护在内部,程序的其它部分只有通过被授权的操作(成员方法),才能对数据进行操作。
  • JS封装只有两种状态,一种是公开的,一种是私有的。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    function Person(name,agei,sal){
    this.name=name;//公开属性
    var age=agei;//私有属性
    var salary=sal;//私有属性
    //在类中如何定义公开方法(特权方法),私有方法(内部方法)
    //如果我们希望操作私有的属性,则可用公开方法实现
    this.show=function(){
    window.alert(age+" "+salary)
    }
    //私有方法,可用访问对象的属性
    function show2(){
    window.alert(age+" "+salary);
    }
    }
构造函数方式 与 原型方式给对象添加方法的区别:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//1.通过构造函数方式给对象添加方法
function Dog(name){
this.name=name;
this.shout=function(){
window.alert("小狗尖叫"+this.name);
}
}
var dog1=new Dog("aa");
var dog2=new Dog("bb");
if(dog1.shout==dog2.shout){
window.alert("相等");
}else{
window.alert("不相等");
}

//会输出“不相等”
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//2.通过原型方式给对象添加方法
function Dog(name){
this.name=name;
}
Dog.prototype.shout=function(){
window.alert("小狗尖叫"+this.name);
}
var dog1=new Dog("aa");
var dog2=new Dog("bb");
if(dog1.shout==dog2.shout){
window.alert("相等");
}else{
window.alert("不相等");
}

//会输出“相等”
  • 以上的两个例子说明通过构造函数来分配成员方法,给每个对象分配一份独立的代码。这样的弊端就是如果对象实例有很多,那函数的资源占用就会很大,而且有可能造成内存泄漏。而原型法是大家共享同一份代码,就不会有那种弊端。
  • 因此,通过构造函数添加成员方法和通过原型法添加成员方法的区别:
    1.通过原型法分配的函数是所有对象共享的;
    2.通过原型法分配的属性是独立的;(如果你不修改属性,他们是共享)
    3.如果希望所有的对象使用同一个函数,最好使用原型法添加方法,这样比较节省内存。
prototype可以给所有对象添加方法,但是这种方式,不能去访问类的私有变量和方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function Person(){
this.name="Cece"; //公开
var age=18; //私有
this.abc=function(){ //公开
window.alert("abc");
}
function abc2(){ //私有
window.alert("abc");
}
}
Person.prototype.fun1=function(){
window.alert(this.name);//Cece
//window.alert(age);//Uncaught ReferenceError: age is not defined(…)
//abc2(); //Uncaught ReferenceError: abc2 is not defined(…)
this.abc(); //abc
}
var p1=new Person();
p1.fun1();
继承
  • 继承可以解决代码复用(冗余)。当多个类存在相同的属性(变量)和方法时,可以从这些类中抽象出父类,在父类中定义这些相同的属性和方法,所有的子类不需要重新定义这些属性和方法,只需要通过继承父类中的属性和方法。
  • JS中实现继承的方式:
    • 对象冒充
    • 通过call或者apply实现
    • 原型继承
    • 混合继承(构造+原型)
    • 多重继承
对象冒充
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
 //1.把子类中共有的属性和方法抽取出,定义一个父类Stu 
function Stu(name, age){
this.name = name;
this.age = age;
this.show = function(){
window.alert(this.name + " " + this.age);
}
}
function MidStu(name, age) {
this.stu = Stu;
// 通过对象冒充实现继承,这句话不能少,因为js是动态语言,如果你不执行,则不能实现继承效果
// 对象冒充,即获取那个类的所有成员,因此MidStu就有了父类Stu的成员
this.stu(name, age);
//MidStu可以覆盖Stu父类的show方法
this.show=function(){
window.alert("MidStu show();");
}
}
function Pupil(name, age) {
this.stu = Stu;
// 通过对象冒充实现继承
this.stu(name, age);
}

var midStu = new MidStu("zs", 13);
midStu.show(); //MidStu自己的show方法
var pupil = new Pupil("ls", 10);
pupil.show(); //父类Stu的show方法
通过call或者apply实现
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
//把子类中共有的属性和方法抽取出,定义一个父类Stu 
function Stu(name,age){
this.name=name;
this.age=age;
this.show=function(){
window.alert(this.name+"年龄是="+this.age);
}
}
//通过call或者apply来继承父类的属性的方法
function MidStu(name,age){
//通过call修改了Stu构造函数的this指向,让它指向了调用者本身.
Stu.call(this,name,age);
//如果用apply实现,则可以
//Stu.apply(this,[name,age]); //说明传入的参数是 数组 方式
//可以写MidStu自己的方法.
this.pay=function(fee){
window.alert("你的学费是"+fee*0.8);
}
}
function Pupil(name,age){
Stu.call(this,name,age);//当创建Pupil对象实例,Stu的构造函数会被执行,当执行后,Pupil对象就获取到从Stu封装的属性和方法
//可以写Pupil自己的方法.
this.pay=function(fee){
window.alert("你的学费是"+fee*0.5);
}
}
//test
var midstu=new MidStu("zs",15);
var pupil=new Pupil("ls",12);
midstu.show();
midstu.pay(100);
pupil.show();
pupil.pay(100);
混合继承(构造+原型)
  • 混合继承是把多种继承方式一起使用,发挥各个优势,来实现各种复杂的应用。最常见的就是把构造函数和原型继承一起使用。做法是将需要独立的属性方法放入构造函数中,而可以共享的部分则放入原型中,这样做可以最大限度节省内存而又保留对象实例的独立性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function A(x,y){  //属性写到构造函数中
this.x = x;
this.y = y;
}
A.prototype.add = function(){ //方法写到原型中
return (this.x-0) + (this.y-0);
}
function B(x,y){
A.call(this,x,y);
}
B.prototype = new A();

// 测试
var b = new B(2,1);
console.log(b.x); // 2
console.log(b.add()); // 3
  • 注意:
    1. 方法写在原型中比写在构造函数中消耗的内存更小,因为在内存中一个类的原型只有一个,写在原型中的行为可以被所有实例共享,实例化的时候并不会在实例的内存中再复制一份; 而写在构造函数中的方法,实例化的时候会在每个实例中再复制一份,所以消耗的内存更高。所以一般把属性写到构造函数中,而方法写到原型中。
    2. 构造函数中定义的属性和方法要比原型中定义的属性和方法的优先级高,如果定义了同名称的属性和方法,构造函数中的将会覆盖原型中的。
多态
JS的函数重载
  • 函数重载是多态的基础,JS函数不支持重载(即,不可以通过参数的个数来决定调用哪个函数),但是事实上JS函数是无态的,支持任意长度,类型的参数列表。
  • 如果同时定义了多个同名函数,则以最后一个函数为准。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    /*****************说明js不支持重载*****/
    function Person(){
    this.test1=function (a,b){
    window.alert('function (a,b)');
    }
    this.test1=function (a){
    window.alert('function (a)');
    }
    }
    var p1=new Person();
    //js中不支持重载.
    //但是这不会报错,js会默认是最后同名一个函数,可以看做是后面的把前面的覆盖了。
    p1.test1("a","b");
    p1.test1("a");
  • js可通过判断参数的个数来实现重载

    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
    //js怎么实现重载.通过判断参数的个数来实现重载 
    function Person(){
    this.test1=function (){
    if(arguments.length==1){
    this.show1(arguments[0]);
    }else if(arguments.length==2){
    this.show2(arguments[0],arguments[1]);
    }else if(arguments.length==3){
    this.show3(arguments[0],arguments[1],arguments[2]);
    }
    }
    this.show1=function(a){
    window.alert("show1()被调用"+a);
    }
    this.show2=function(a,b){
    window.alert("show2()被调用"+"--"+a+"--"+b);
    }
    function show3(a,b,c){
    window.alert("show3()被调用");
    }
    }
    var p1=new Person();
    //js中不支持重载.
    p1.test1("a","b");
    p1.test1("a");
  • 重写:子类可以重新写函数,来覆盖父类的某个方法
多态
  • 多态是指一个引用(类型)在不同情况下的多种状态。
  • 也可以理解成:多态是指通过指向父类的引用,来调用在不同子类中实现的方法。
    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
    function Master(){
    //给动物喂食
    this.feed=function(animal,food){
    document.write("主人给"+animal.name+"喂"+food.name);
    }
    }
    //写食物
    function Food(name){
    this.name=name;
    }
    function Fish(name){
    this.food=Food;//对象冒充
    this.food(name);
    }
    function Bone(name){
    this.food=Food;//对象冒充
    this.food(name);
    }
    //动物
    function Animal(name){
    this.name=name;
    }
    function Cat(name){
    this.animal=Animal;//对象冒充
    this.animal(name);
    }
    function Dog(name){
    this.animal=Animal;//对象冒充
    this.animal(name);
    }
    var cat=new Cat("小猫咪");
    var dog=new Dog("小狗");
    var fish=new Fish("小鱼");
    var bone=new Bone("骨头")
    var master=new Master();
    master.feed(cat,fish);

闭包(closure)

  • 闭包可以用在许多地方。它的最大用处有两个:

    • 可以读取函数内部的变量
    • 让这些变量的值始终保持在内存
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      function f1(){
      var n=999;
      nAdd=function(){//全局函数
      n+=1
      };
      function f2(){
          alert(n);
        }
        return f2;
      }
        var result=f1();//闭包f2函数
        result(); // 999
        nAdd();
        result(); // 1000

    可见,函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除。

  • 有权访问另一个函数作用域内变量的函数都是闭包。一般是定义在外层函数中的内层函数。
  • 使用闭包的原因:局部变量无法共享和长久的保存,而全局变量可能造成变量污染,所以我们希望有一种机制既可以长久的保存变量又不会造成全局污染。
  • 下面代码有助于更好地了解闭包机制
1
2
3
4
5
6
7
8
9
10
11
//代码片段一
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
return function(){
return this.name;
};
}
};
alert(object.getNameFunc()());//The Window
1
2
3
4
5
6
7
8
9
10
11
12
//代码片段二
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
var that = this;
return function(){
return that.name;
};
    }
  };
alert(object.getNameFunc()());//My Object

JavaScript的内部类

JavaScript中本身有提供一些可以直接使用的类,这种类就是内部类,主要有:
Object/Array/Math/Boolean/String/RegExp/Date/Number

分类:
  • 静态类:类名.属性/方法

    1
    window.alert(Math.abs(-12));
  • 动态类:使用var对象=new 动态类()对象.属性/方法

    1
    2
    var nowdata=new Data();
    window.alert(nowdate.toLocaleString());
------ 本文结束 ------