第二章 面向对象编程之封装

封装

首先说明封装的概念,在面向对象编程中,封装是将对象运行所需的资源封装在程序对象中——基本上,是方法和数据。封装的目的是增强安全性和简化编程,使用者不必了解具体的实现细节,而只是要通过外部接口,以特定的访问权限来使用类的成员。

普通封装

var Book = function(id,bookname,price){
    
    
    this.id = id;
    this.bookname = bookname;
    this.price = price;
    this.getId = function(){
    
    
        console.log(this.id)
    }
}
Book.prototype = {
    
    
    page:0,
    display:function(){
    
    
        console.log('show this book!')
    }
}

之前的文章中说过JavaScript中并没有类class的概念,但是通过new 构造函数的方式进行实例化。比如,上面的Book就是一个构造器,我们可以通过

var book = new Book(1,'JavaScript设计模式',59)

实例化一个book实例对象。此时我们可以将构造器Book当做一个类来看待。而其中的id,book,price和page就是对象运行所需要的数据,getId和display方法就是对象运行所需要的方法。至于直接使用this或者prototype声明的数据和方法有什么区别,具体可以查看prototype和__proto__的来历和使用这篇文章。

模拟Java中的类的权限(private、protected、public)

var Book = function(id,bookname,price){
    
    
    // 私有属性,private权限,外部无法访问
    var num = 1;
    // 对象公有属性,public权限,外部可以通过.直接访问
    this.id = id
    // 私有方法,private权限
    function checkId(){
    
    
        console.log('check id!')
    }
    // 对象公有方法:protected和public权限,可以访问到private权限的属性和方法(即使用this创建的方法,不仅可以访问对象公有属性,还可以访问对象私有属性和方法)
    // 特权方法:对象公有方法可以访问还可以访问私有属性和方法,此时该对象公有方法称为特权方法
    this.copy = function(){
    
    
        console.log(num)
        checkId()
        alert(this.id)
    }
    // 一般对象公有方法,此时只访问公有属性和方法
    this.getId = function(){
    
    
        return this.id
    }
    this.setId = function(newId){
    
    
        this.id = newId
    }
    this.setBookname = function(newName){
    
    
        this.bookname = newName
    }
    this.setPrice = function(newPrice){
    
    
        this.price = newPrice
    }
    // 构造器:调用初始化实例对象属性的方法,不过一般不用,而是直接通过this.id = id的方式
    this.setBookname(bookname)
    this.setPrice(price)
}

(1)JavaScript通过函数级作用域的特征来实现在函数内部创建外部访问不到的私有变量和私有方法
(2)通过new关键字实例化对象时,由于对类执行一次,所以类的内部this和是哪个定义的属性和方法自然就复制到新创建的对象上,成为对象的公有化属性和方法。
(3)而其中的一些公有方法能够访问到类的私有属性和方法,因此成为特权方法
(4)公有方法的权限是protected还是public是取决于它的使用情况,即是否访问私有变量或者方法

类静态公有属性和方法

类静态公有属性(对象不能访问),可以通过类自身访问
在这里插入图片描述
类静态公有方法(对象不能访问),可以通过类自身访问
在这里插入图片描述

闭包

闭包的用处

要了解闭包的概念,首先就得知道闭包是用来做什么用的。有些场景下,某些变量我们希望不能直接暴露在用户下,但是又能对这些进行操作。比如我们现在开发一个简单的射击游戏,我们刚开始有3条命,在某些条件下可以增加或者减少玩家的命。而“命”这个变量我们就不能直接暴露在全局window下,所以就需要将“命”这个变量作为私有变量放在类中。

function Player(){
    
    
	var lives = 3
}

那接下来我们如何增加或者减少命lives这个变量呢?这就是闭包的用处了。我们需要在Player这个类中增加一个私有函数用于访问lives并且增加或者减少,并且想办法将它暴露给外部使用。

function Player(){
    
    
    var lives = 3
    // 加命函数
    function addLife(){
    
    
        lives = lives + 1
        return lives
    }
    return addLife
}

在这里插入图片描述
可以看到,在Player中我们将加命方法addLife通过return的方式暴露给外部使用,而lives也因为被addLife引用而导致一直存在内存中,从而达到局部变量能够被使用的目的。
在这里插入图片描述
并且每次创建新实例时,都会创建一个新的lives存在于内存中,与其他实例互不影响。

闭包一定要使用return吗

不一定。闭包的目的是将局部变量暴露给外部,如果你愿意,也可以将return修改成window.的方式将局部方法和变量暴露给外部。

function Player(){
    
    
    var lives = 3
    window.addLife = function(){
    
    
        lives = lives + 1
        return lives
    }
}

在这里插入图片描述
但是这样会有一个弊端,就是每次声明新实例的时候,window.addLife都会被重新赋值一次,其中的局部变量指向,也会由原来实例(palyer1)局部变量指向新的实例(player)局部变量。从而导致看上去就像各个玩家的命都是共享的,每次只要创建一个新的实例,所有玩家的都会重新变成4条(3+1)。
当然,我们也可以通过其他技术手段解决这个弊端,比如让addLife变成一个全局数组,每次创建新的Player实例的时候,都让addLife下标加1。
在这里插入图片描述
不过这样明显没有使用return方式(将加命方法直接挂在在Player实例对象)来的优雅和方便,所以我们才经常使用return的方式来进行使用和用法的说明。但是实际上,只要能够通过函数中的函数访问外部函数的局部变量,这样就是闭包。管你使用rerun还是window.或者其他方式。

说实话,其实我们要学习闭包才能够使用吗?肯定不是,闭包只是为了阐述如何如何将变量隐藏并且用特定方式使用而引申出的概念,也可以说是函数级作用域的一个副产物。我们也许不知道闭包的概念,但是在真实开发中或许就会遇到这样的场景并且使用了闭包也未可知。

不要迷信复杂的东西,实际上编程是崇尚简洁和优美的。如果一个概念你认为过于复杂,或许是你理解错了,也或许是这个技术本身就不够简洁和优美,有待优化。

一定要使用闭包吗

其实这句话有问和没问一样,在你进行真实项目开发的时候,自然就会进行判断,一个变量是否应该使用私有变量进行隐藏和使用局部函数进行访问,或者说一个私有变量是否应该使用闭包将它存在在内存中。不要被闭包这个概念所束缚和吓到,甚至你都没有必要知道闭包。这些概念存在的目的是为了帮助我们理解和开发,而不应该成为阻碍。

当然,了解闭包的概念还是有一定用处的,至少面试官在面试的时候都喜欢问这些有的没的。并且在别人都弄不明白闭包是什么的情况下,你懂,就显得你优秀一些。就像你会降龙十八掌,不知道或者忘记武功招式的名字你就不厉害了吗?也不是。但是如果你在用这些功夫的使用喊一下“亢龙有悔、飞龙在天”之类的,是不是听起来比较有排面呢?

使用闭包实现类的静态变量

var Book = (function(){
    
    
    // 类的静态私有变量
    var bookNum = 0
    // 类的静态私有方法
    function checkBook(name){
    
    
        console.log(name)
    }
    function _book(id,bookname,price){
    
    
        // 私有属性,private权限,外部无法访问
        var num = 1;
        // 对象公有属性,public权限,外部可以通过.直接访问
        this.id = id
        // 私有方法,private权限
        function checkId(){
    
    
            console.log('check id!')
        }
        // 对象公有方法:protected和public权限,可以访问到private权限的属性和方法(即使用this创建的方法,不仅可以访问对象公有属性,还可以访问对象私有属性和方法)
        this.copy = function(){
    
    
            console.log(num)
            checkId()
            alert(this.id)
        }
        // 对象公有方法
        this.getId = function(){
    
    
            return this.id
        }
        this.setId = function(newId){
    
    
            this.id = newId
        }
        this.setBookname = function(newName){
    
    
            this.bookname = newName
        }
        this.setPrice = function(newPrice){
    
    
            this.price = newPrice
        }
        bookNum ++
        if(bookNum > 5){
    
    
            throw new Error('我们仅出版5本书')
        }
        // 构造器:调用初始化实例对象属性的方法
        this.setBookname(name)
        this.setPrice(price)
     }
    // 构建原型
     _book.prototype = {
    
    
        // 静态共有属性
        isJSBook:false,
        // 静态共有方法
        display:function(){
    
    
            console.log('show this book')
        }
     }
     return _book
})()

在这里插入图片描述

创建对象的安全模式

对于初学者而言,在创建对象的时候由于不适应new的写法,所以经常忘记使用new而犯错。

var Book = function(title,time,type){
    
    
    this.title = title;
    this.time = time;
    this.type = type;
}
var book1 = Book('JavaScript设计模式','2021/04/04','js')

在这里插入图片描述
在上面的例子中我们没有使用new去实例化Book,会发生什么呢?或者说book1这个变量会是什么,并且Book('JavaScript设计模式','2021/04/04','js')究竟做了什么?

通过输出book1可以发现值是undefined,为什么会这样呢?其实了解new关键字的意义就明白了。new的意义就是将当前对象的this不停地赋值,如果没有使用new,那么this指向的就是全局对象,输出window:
在这里插入图片描述
因为函数也没有return语句,所以这个Book自然也不会告诉book1变量的执行结果了, 所以是undefined(未定义)。

那就使用安全模式:

var Book = function(title,time,type){
    
    
    if(this instanceof Book){
    
    
        this.title = title;
        this.time = time;
        this.type = type;
    }else{
    
    
        return new Book(title,time,type)
    }
}
var book1 = Book('JavaScript设计模式','2021/04/04','js')

在这里插入图片描述
这样我们就再也不用担心因为忘记使用new关键字的问题了。

下一篇 面向对象编程之继承

猜你喜欢

转载自blog.csdn.net/qq_39055970/article/details/115420490