对JavaScript的面向对象以及闭包的认识

面向对象

几种模式

工厂模式
  • 接收参数,内部构造一个对象并return
function Function1(args){
    var obj = new Object();
    obj.args = args;
    //添加方法
    obj.littleFuction = function() {
        
    }
    return obj;
}
构造函数模式

特点:

  • 不显式的创建对象,直接将方法、属性赋给this对象
  • 不return
  • 需要实例化(new)
  • 每个成员无法得到复用

Tips: 任何函数如果通过new操作符来调用,就可以作为构造函数;如果不用new操作符,则当普通函数(this则指向Global对象,浏览器中即window对象)

function Function2(args) {
    this.args = args;
    //添加方法
    this.littleFuction = function() {
        
    }
}
//使用
var test = new Function2(args);
原型模式

每个对象都有一个prototype属性,该属性是一个指针,指向一个包含所有实例共享的属性和方法的对象(原型对象)。

constructor属性:在自定义构造函数后,原型对象会默认取得改属性,constructor是指向prototype属性所在函数的指针。

Tips:

  • 对象实例添加与原型对象同名属性时,不能重写,只会覆盖;delete操作符可删除实例属性。
  • 原型具有动态性,实例的创建和原型之间是松散的,即先创建实例后修改原型也ok。随时可为原型添加属性和方法。
    • 特别注意:如果重写原型对象(用对象字面量重写——object.prototype = { }),会切断现有原型与任何之前已经存在实例之间的联系。即原先new的实例无法引用的还是最初的原型。

原型对象存在的问题:

  • 所有实例共享属性和方法,对于引用类型值就不可独立开来,容易出现问题
function Function3() {
}
//给原型添加属性
Function3.prototype.args = new_args;
//给原型添加方法
Function3.prototype.littleFuction = function() {
    alert(this.args);//this指向的就是原型
}
//创建实例
var instance = new Function3();

instance.args = another_args;//只是这个实例的属性会覆盖掉原型的属性

instance.prototype = {
    agrs: another_args
}//这样则会重写原型属性
组合使用构造函数模式和原型模式
  • 构造函数模式用于定义实例属性
  • 原型模式用于定义方法和共享的属性
//构造函数模式
function Function4(args) {
    //定义实例属性
    this.args = args;
}
//原型模式
Function4.prototype = {
    share_args: share_args;
    littleFuction : function() {
    	alert(this.args);
	}
}
动态原型模式

通过构造函数来初始化原型。

通过 if 语句判断在调用构造方法时,参数是否有传某个方法,来决定是否需要初始化原型(就是采用原型模式来添加方法或属性)。

寄生构造函数模式

工厂模式 + new 操作符实例对象,调用构造函数(其实就是工厂模式的函数)

返回的对象与构造函数或者构造函数的原型属性之间没有关系

稳妥构造函数模式

与寄生构造函数模式类似,不同之处在于:

  • 新创建的对象的实例方法不引用this
  • 不使用new调用构造函数

其实就是函数传进来的值无法直接访问,因为没有了this和new,没有引用指向这些值。

想访问值可以添加方法,类似java的getter和setter

优点:数据比较安全

继承

ES支持实现继承,不支持接口继承。而实现继承主要靠原型链实现

原型链

何为原型链?

  • 其实就是让原型对象包含的指向构造函数的内部指针,改为指向另一个原型对象,从而层层递进,形成原型链

  • 代码上实现就是将 子类原型的prototype指针 指向 超类原型的实例

    //超类原型
    function SuperType() {  
    }
    //子类原型
    function SubType() {
    }
    //继承 SuperType
    SubType.prototype = new SuperType();
    

事实上,所有的函数默认原型都是Object的实例,所以默认原型都会包含一个内部指针,指向Object.prototype。

我们也可以知道访问实例属性的本质:一步一步往上直至原型链末端搜索查找

Tips

  • 给原型添加方法一定要放在替换原型的语句之后。即超原型的实例赋给子原型后,再添加(重写)子原型或超原型方法
  • 通过原型链实现继承时,不能使用对象字面量创建原型方法。
  • 在创建子类型的实例时,不能向超类型的构造函数中传递参数
借用构造函数

在子类型构造函数内部调用超类型

代码上实现:超类型函数调用 call(this, args) 或 apply(this, args) 方法。可传参数,意味着相对原型链有个很大的优势,可以在子类构造函数中向超类构造函数传递参数

缺点:方法都在构造函数中定义,无法函数复用。

//超类原型
function SuperType() {  
}
//子类原型
function SubType() {
    //继承 SuperType
    SuperType.call(this);
}
组合继承

实质就是把原型链和借用构造函数组合使用

Tips:

  • 无论什么情况下都会调用两次超类型函数
    • 创建子类型原型时
    • 子类型构造函数内部调用 call() 或 apply() 方法
//超类原型
function SuperType(args) {  
	this.args = args;
}
//子类原型
function SubType(args, another_args) {
    //继承 SuperType 的属性
    SuperType.call(this, args);
    //也能拥有自己的属性
    this.another_args = another_args;
}
//继承 SuperType
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
原型式继承

可以在不必预先定义构造函数的情况下实现继承,本质是执行给定对象的浅复制。复制得到的副本可以进一步改造。

必须要有一个对象作为另一个对象的基础。

var object = {
    agrs: "args";
}
var another_object = Object.create(object);//create方法是es5新增的
var another_object = Object(object);//或者这样

可以做到让一个对象与另一个对象保持类似

寄生式继承

基于某个对象或某些信息创建一个对象,然后增强对象,最后返回对象。

function creatObject(original) {
    var clone = Object(original);
    //可接着添加属性或方法,属于自己的,与源对象独立的
    return clone;
}
var object = {
    agrs: "args";
}
var another_object = creatObject.create(object);

与寄生构造函数和工厂模式类似

寄生组合式继承

通过借用构造函数来继承属性,通过原型链的混成形式来继承方法

function inheritPrototype(subType, superType) {
    var prototype = object(superType.prototype);//创建对象
    prototype.constructor = subType; //增强对象
    subType.prototype = prototype; //指定对象
}

寄生组合式继承是实现基于类型继承的最有效方式


函数与闭包

js 定义函数的形式有两种

  • 函数声明
  • 函数表达式

了解函数声明提升

​ 函数声明提升:在执行代码前会先读取函数声明。即代码顺序可以先调用再声明。但只对函数声明有效,对函数表达式则不行。

对于函数表达式(匿名函数),使用前必须先赋值。

变量作用域

  • 函数内部可以访问外部变量,而函数外部无法读取函数的局部变量
  • 没有用var声明的变量,默认为全局变量

每个函数被调用时,会创建他的执行环境,且执行环境有相应的作用域链,作用域链本质上是一个指向变量对象的指针列表,只引用不包含变量对象。而变量对象又有本地活动对象和全局变量对象之分。

匿名函数的执行环境具有全局性

何为闭包?

闭包指有权访问另一个函数作用域中的变量的函数

可以把闭包简单理解成"定义在一个函数内部的函数"。

function f1() {
    var args = 'hello';
    return function() {
        alert(args);
    }
}

函数 f1 返回的匿名函数就是闭包。

需要注意的是,匿名函数从 f1() 被返回后,它的作用域是包含f1的局部活动对象和全局活动对象的。而且,f1()函数执行完后,其执行环境的作用域链会被销毁,但它的活动对象依然留在内存中,只有直到匿名函数被销毁它才会销毁。

var res = f1();//创建函数

var res2 = res();//调用函数(匿名函数)

res = null;//解除对匿名函数的引用,以便垃圾回收机制回收,释放内存

Tips:

  • 前面说过,闭包会引用包含函数的整个活动对象,也就是说闭包会使得函数中的变量都被保存在内存中,这样内存消耗很大,因此不能滥用闭包。

  • 匿名函数的this对象通常指向window(执行环境具有全局性),但要视具体情况。下面是书上的例子:

    var name = "The Window";
    //第一个例子 
      var object = {
        name : "My Object",
    
        getNameFunc : function(){
          return function(){
            return this.name;
          };
        }
      };
      alert(object.getNameFunc()());//"The Window"
    
    //第二个例子
      var object = {
        name : "My Object",
    
        getNameFunc : function(){
          var that = this;
          return function(){
            return that.name;
          };
        }
      };
      alert(object.getNameFunc()());//"My Object"
    
    
    • 第一个例子:调用 getNameFuncth 后,返回匿名函数。因为每个函数被调用时,会自动取得 thisargument 两个特殊变量。而内部函数搜索这两个变量时,只会搜索到其活动对象为止。因此访问不到外部函数中的 thisargument
    • 第二个例子:因为把 this 赋值给 that ,函数返回后,that 仍然引用着 object ,所以闭包可以访问。

闭包的用途

模仿块级作用域

js本身没有块级作用域的概念,可以用匿名函数模仿

  1. 创建并立即调用一个函数
  2. 执行完,不会在内存中留下对该函数的引用,函数内部的所有变量都会被立即销毁(除了那些被赋值给外部作用域的变量)。
//第一种
var blockFunction = function() {
    //块级作用域
}
blockFunction();//立即调用

//第二种 函数表达式
(function() {
    //块级作用域
})();
在对象中创建私有变量

相关概念:

  • 特权方法:有权访问私有变量和私有函数的公有方法。

在函数内部创建闭包,可以利用闭包创建特权方法。

有如下两种方法:

  • 第一种,在构造函数中定义特权方法
function MyFunction() {
    //私有变量 
    var privateVariable = 1;
    //私有函数
    function privateFunction() {
        return "I'm a private function"
    }
    //特权方法 其实就是个闭包
    this.publicMethod = function() {
        //访问私有变量
        privateVariable++;
        //返回私有函数
        return privateFunction;
    }
}

​ 优点:可以做到私有变量只能通过特权方法才能访问和修改。

​ 缺点:因为采用构造函数模式,对每个实例都会创建同样一组新方法。

  • 第二种,使用原型模式,在私有作用域中定义私有变量或函数。
(function() {
    //私有变量 
    var privateVariable = 1;
    //私有函数
    function privateFunction() {
        return "I'm a private function"
    }
    //构造函数 没有用var声明,会变成全局变量,但严格模式下会报错
    MyFunction() {
    }//特权方法
    MyFunction。prototype.publicMethod = function() {
        //访问私有变量
        privateVariable++;
        //返回私有函数
        return privateFunction;
    }
})();

​ 优点:私有变量和私有函数所有实例共享。

​ 缺点:每个实例没有自己的私有变量。

使用模块模式为单例创建私有变量和特权方法
var singleton = function() {
    //私有变量 
    var privateVariable = 1;
    //私有函数
    function privateFunction() {
        return "I'm a private function"
    }
    //返回对象的匿名函数
    return {
        //公有属性
        publicProperty: "I'm a public property"
        //特权方法
        publicMethod : function() {
            //访问私有变量
            privateVariable++;
            //返回私有函数
            return privateFunction;
        }
    }
}();

使用场景:必须创建一个对象并以某些数据对其进行初始化,同时还需要公开一些能够访问私有数据的方法。

增强的模块模式

var singleton = function() {
    //私有变量 
    var privateVariable = 1;
    //私有函数
    function privateFunction() {
        return "I'm a private function"
    }
    //创建对象,是某种类型(xxx)的实例
    var object = new xxx();
    
    //公有属性
    object.publicProperty = "I'm a public property"
    //特权方法
    object.publicMethod = function() {
        //访问私有变量
        privateVariable++;
        //返回私有函数
        return privateFunction;
    }//返回该对象
    return object;
}();

适用于单例必须是给某种类型的实例

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值