[JavaScript]理解JavaScript函数,类继承

前言:本篇博客偏向理解总结,初学者不友好。

一、JavaScript函数

  1. js函数函数的声明:
  • 一般的函数声明
function f(){
}
  • 匿名函数声明
var f = function(){
}
  1. 函数的调用
  • 直接使用函数名调用(这种调用方法是最直接也是最直观的调用方法,适合用作普通的方法函数的调用)
f();
  • 使用new关键字进行函数调用(这种调用方式适合用于创建对象,进行对象构造时使用)
var fun = new f()

这里声明了一个f对象,并将fun指向了f对象。

JavaScript对构造函数和普通方法函数的划分是逻辑上的,并没有对两者进行显示的区分,这种划分需要编码者根据实际需求进行运用。
Function = 类(或者叫类构造器) + 普通函数
类 = 变量 + 方法
这些内容在一开始困扰了我好久,因为习惯了Java的OOP,对于JS这种并没有严格划分类,方法和普通函数的用法感觉比较绕,但是缕清之后就好了,因为JS对这些内容的划分是逻辑上的。但是也遵循一些格式规范以便增加可读性,比如用作类的function首字母大写,不然就真的傻傻分不清楚了。

二、函数的类属性和实例属性

1. 实例属性(可以类比Java的实例属性进行理解):

在方法中,尤其是被用作构造方法的方法会经常使用this关键字,this关键字表示该属性是当前函数实例范围内的实例属性,该属性属于当前所在的function。若要获得某个对象的实例属性,需要用对象名.属性名的方式进行访问。

2. 类属性(可以类比Java的静态域进行理解):

类属性属于当前本function,也就是本类,不属于某一个特定的实例。因此对类属性的访问需要采用 类名(function名).类属性 的方式对其进行访问。

注意:因为JavaScript并不严格限制变量先声明后使用,也不限制变量必须先显式的声明,所以在编码时要对实例属性和类属性进行区分,避免出现undefine的情况。类属性和实例属性完全是两个完全不同的概念,就像Java静态变量的实例变量是完全不同的概念

三、区分一个很重要的问题:

使用 new 和不new 的区别:
function doAdd(iNum) {
  alert(iNum + 20);
}

function doAdd(iNum) {
  alert(iNum + 10);
}

doAdd(10);	//输出 "20"

如你所知,第二个函数重载了第一个函数,使 doAdd(10) 输出了 “20”,而不是 “30”。
如果以下面的形式重写该代码块,这个概念就清楚了:

请观察这段代码,很显然,doAdd 的值被改成了指向不同对象的指针。函数名只是指向函数对象的引用值,行为就像其他对象一样。甚至可以使两个变量指向同一个函数:

var doAdd = new Function("iNum", "alert(iNum + 20)");
var doAdd = new Function("iNum", "alert(iNum + 10)");
doAdd(10);

四、继承机制的实现

直接函数调用(多用于普通函数的调用)

就是我们最常见,直接进行声明,并可以直接调用的函数,从属于window

call方式和apply方式(用于继承机制的实现)

因为Js继承的存在,一些方法是属于父类的,子类进行父类的调用时直接调用就会存在一些问题,call和apply很好的解决了这个问题。
继承机制的实现:

  • 对象冒充
function ClassA(sColor) {
    this.color = sColor;
    this.sayColor = function () {
        alert(this.color);
    };
}

function ClassB(sColor, sName) {
    this.newMethod = ClassA;
    this.newMethod(sColor);
    delete this.newMethod;

    this.name = sName;
    this.sayName = function () {
        alert(this.name);
    };
}

var objA = new ClassA("blue");
var objB = new ClassB("red", "John");
objA.sayColor();	//输出 "blue"
objB.sayColor();	//输出 "red"
objB.sayName();		//输出 "John"
多重继承:

在这里插入图片描述

function ClassZ() {
    this.newMethod = ClassX;
    this.newMethod();
    delete this.newMethod;

    this.newMethod = ClassY;
    this.newMethod();
    delete this.newMethod;
}

对象冒充的方式直接将ClassA的内容拿到ClassB中,并调用A的构造方法进行了B的初始化操作。

  • call调用
    function ClassB(sColor, sName) {
        //this.newMethod = ClassA;
        //this.newMethod(color);
        //delete this.newMethod;
        ClassA.call(this, sColor);
    
        this.name = sName;
        this.sayName = function () {
            alert(this.name);
        };
    }

applly调用和call类似,只是将调用的参数列表换为了数组。

  • 原型链
    prototype 对象是个模板,要实例化的对象都以这个模板为基础。总而言之,prototype 对象的任何属性和方法都被传递给那个类的所有实例。原型链利用这种功能来实现继承机制。
    如果用原型方式重定义前面例子中的类,它们将变为下列形式:
function ClassA() {
}

ClassA.prototype.color = "blue";
ClassA.prototype.sayColor = function () {
    alert(this.color);
};

function ClassB() {
}

ClassB.prototype = new ClassA();

这里,把 ClassB 的 prototype 属性设置成 ClassA 的实例。这很有意思,因为想要 ClassA 的所有属性和方法,但又不想逐个将它们 ClassB 的 prototype 属性。还有比把 ClassA 的实例赋予 prototype 属性更好的方法吗?
与对象冒充相似,子类的所有属性和方法都必须出现在 prototype 属性被赋值后,因为在它之前赋值的所有方法都会被删除。为什么?因为 prototype 属性被替换成了新对象,添加了新方法的原始对象将被销毁。所以,为 ClassB 类添加 name 属性和 sayName() 方法的代码如下:

function ClassB() {
}

ClassB.prototype = new ClassA();

ClassB.prototype.name = "";
ClassB.prototype.sayName = function () {
    alert(this.name);
};

此外,在原型链中,instanceof 运算符的运行方式也很独特。对 ClassB 的所有实例,instanceof 为 ClassA 和 ClassB 都返回 true。

var objB = new ClassB();
alert(objB instanceof ClassA);	//输出 "true"
alert(objB instanceof ClassB);	//输出 "true"

所以仅用prototype实现的继承是伪继承。

  • 混合方式
function ClassA(sColor) {
    this.color = sColor;
}

ClassA.prototype.sayColor = function () {
    alert(this.color);
};

function ClassB(sColor, sName) {
    ClassA.call(this, sColor);
    this.name = sName;
}

ClassB.prototype = new ClassA();

ClassB.prototype.sayName = function () {
    alert(this.name);
};

var objA = new ClassA("blue");
var objB = new ClassB("red", "John");
objA.sayColor();	//输出 "blue"
objB.sayColor();	//输出 "red"
objB.sayName();	//输出 "John"

在此例子中,继承机制由两行

ClassA.call(this, sColor);
ClassB.prototype = new ClassA();

在第一行中,在 ClassB 构造函数中,用对象冒充继承 ClassA 类的 sColor 属性。在第二行中,用原型链继承 ClassA 类的方法。由于这种混合方式使用了原型链,所以 instanceof 运算符仍能正确运行。

五、总结

我们要深入理解Js函数之间几种使用方法的差异,普通函数调用和对象方法调用,以及用于类构造器的函数的使用,理清他们之间使用上的差异。JavaScript的类继承方式总结来说是在努力向Java的OOP来靠拢,从(四)混合方式实现类继承的混合方式可以看出,用Java的思想去理解该种实现方式就会很容易理解。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值