从零开始学_JavaScript_系列(61)——class(2)私有方法、this

4、私有

4.1、私有方法

不得不遗憾的表示,目前来看,类似c++或者java里的那种私有方法和私有变量,在js里面还是实现不了的。

因此变通方法有三个:

1、伪私有:

下划线开头表示私有方法。如代码:

class Foo {
    _testPrivate() {

    }
}

这种私有方法是一种约定俗称的私有方法命名方式

用下划线开头,表示告诉使用者,这些是私有方法,你最好不要改,改了有可能出错。但实际中,如果用户想要访问,还是可以访问到的。

2、作用域改变:

把私有方法移到类之外的同级作用域(或上级作用域中),当使用的时候,通过直接调用该方法,或者通过fun.call(this, ..arg)这样的方式调用。

由于该方法位于类的同级或上级作用域,因此类是可以访问到的;

而在使用类的实例的时候,便可能因为作用域的不同,无法调用该方法,因此达到私有方法的作用。

如代码:

function test() {
    let formatTime = function () {
        let date = this.date
        let hour = date.getHours()
        let min = date.getMinutes()
        let sec = date.getSeconds()
        let milliseconds = date.getMilliseconds()
        return `${hour}:${min}:${sec} ${milliseconds}ms`
    }
    class Foo {
        constructor() {
            this.date = new Date()
        }

        showCreateTime() {
            console.log(formatTime.call(this))
        }
    }
    this.getFoo = function () {
        return new Foo()
    }
}

let p = (new test()).getFoo()
p.showCreateTime()
setTimeout(function () {
    let q = (new test()).getFoo()
    q.showCreateTime()
}, 1000)
// 22:18:26 252ms
// 22:18:27 262ms

在外层作用域,显然是访问不到formatTime这个函数的,因此达成了私有函数的效果。

如果是模块的话,通过模块导出Foo这个类后,其他方法显然是无法访问到这个模块里的任何非被导出函数的。

3、Symbol变量作为方法名

关于Symbol的知识请复习这篇博客Symbol简述

简单来说,通过Symbol生成的变量,他是唯一的。换句话说,只要你用的不是我这个Symbol的变量,那么你自己怎么生成,都无法生成一个和我这个Symbol变量相等的变量。

Symbol() !== Symbol()   // true

除此之外,Symbol变量可以作为对象/类的key使用。

因此当我生成一个Symbol变量作为类的方法名,如果你访问不了我这个Symbol变量,那么你必定无法调用这个方法。

如代码:

function Foo() {
    const bar = Symbol()
    class Foo {
        [bar]() {
            console.log("this is private")
        }

        test() {
            this[bar]()
        }
    }
    return new Foo()
}

let p = new Foo()
p.test()

在以上代码中,你只能通过p.test()来间接调用p[bar]这个方法,但却不能直接通过p[bar]来调用这个方法。

因此,达成了私有方法的效果。

4.2、私有变量

简单来说,目前不支持私有变量写法,甚至不支持把变量写在和类的方法平级的地方。

有提案说可以用#放在变量名前,作为私有变量,但所谓提案,就是现在还不行,就是这样。

如果只是创建变量(而非私有变量),那么一个变相的解决办法是可以专门用一个方法,来初始化该变量。然后在constructor函数中调用他即可。

另一个解决办法是使用es5的setter和getter特性来实现变量(非私有变量),具体参照第六部分

如代码:

class Foo {
    constructor() {
        this.createPrivateVarible()
    }

    createPrivateVarible() {
        this.a = 1
        this.b = "a"
    }
}
let p = new Foo()
console.log(p.a, p.b)   // 1 "a"

如果要创建私有变量,那么可以参照私有方法的办法。

function Foo() {
    const p = 1
    class Foo {
        test() {
            console.log(p)
        }
    }
    return new Foo()
}

let p = new Foo()
p.test()

5、this

5.1、默认情况

this可真是让人又爱又恨。

不过搞清楚this的话,可以避免很多错误,还可以提升自己对要学习内容的认识。

在class里,this默认情况下,指向的是当前实例

如代码:

class Foo {
    test() {
        return this
    }
}
let p = new Foo()
p.test() === p  // true
5.2、需要调用类的某个方法

由于类的实际实现,目前还是依靠原型链的。

因此,可以通过原型链来调用。

这个时候,this指向类的原型链(而不是类本身);

如果没注意的话,可能会误解指向类本身(记住对象的方法的this指向对象本身就好了)。

如代码:

class Foo {
    test() {
        return this
    }
}
Foo.prototype.test() === Foo.prototype; // true
Foo.prototype.test() === Foo;   // false
5.3、单独提取类的方法

假如我们看上类的某个方法了,想要提取出来,那么这个时候this指向谁呢?

答案是undefined。

原因有二:

  1. 当单独提取出类的方法时,他就是一个函数,而不是一个对象的方法了,因此this不再指向对象本身(对象的方法中的this,指向对象自己);
  2. 如果是一个函数,那么this应该指向window,然而由于类天生是严格模式,因此提取出来的函数也是严格模式的,而严格模式下函数的this,默认是undefined;
class Foo {
    test() {
        return this
    }
}
let {test} = Foo.prototype
test()  // undefined

function bar() {
    "use strict"
    return this
}
bar()   // undefined

与此同理的,是提取类的实例的方法,效果是一样的。

5.4、改变this指向

当知道提取出来的类的方法,this指向的是谁后,如果想改变this指向,那么方法很多啦。

比如什么Fn.callFn.applyFn.bind之类之类的。

5.5、默认this指向类的实例

如果你需要该类的方法,默认指向类的实例,有两种比较常见的解决办法:

  1. 参照5.4中的,在提取出来之后,通过bind来绑定。这个办法的好处在于无需修改原来的类。
  2. 假如可以修改原本的类,那么也可以用一个绑定过this的函数,位于原型链更靠近实例的地方,用于替换未被绑定this的函数;

第二种方法,具体来说,是这样实现的。

1、由于类的方法,是挂在类的原型链上的(例如Foo.prototype.test)这样;

2、因此当通过new生成实例后,该方法实际上是在实例的__proto__属性上(例如bar.__proto__.test)这样;

3、那么假如我直接在实例的属性上挂载一个绑定后的test方法(例如bar.test),那么在获取test方法的时候,会优先获取bar.test,而不是bar.__proto__.test

4、而这个获取的bar.test是绑定了this,让this指向类的实例的;

如示例代码:

class Foo {
    // 在构造函数中,给实例添加一个绑定了this的test方法
    constructor() {
        this.test = this.test.bind(this)
    }

    test() {
        return this
    }
}
let bar = new Foo()
bar;    // Foo {test: ƒ}

let {test} = bar
test() === bar; //true,可以证明test返回的this就是bar本身

除了上面两个常规方法之外,还有两个方法:

1、利用Proxy,在获取函数的时候,自动绑定this。

但有几个问题:

  1. Proxy在一般情况下,使用的比较少,若只是为了这个简单的要求使用Proxy,是把事情复杂化了,必要性不大;
  2. Babel的转换也是一件麻烦的事情。

所以不推荐使用。

2、箭头函数。

箭头函数的原理,是通过一个特性:

箭头函数的this,指向其定义时的作用域,而不是箭头函数在使用时的作用域

只要记住这个特性,那么在理解下面这段代码的时候,就非常容易了:

class Foo {
    constructor() {
        this.test = () => {
            return this
        }
    }

    test() {
        return this
    }
}
let bar = new Foo()
bar;    // Foo {test: ƒ}

let {test} = bar
test() === bar; //true

顺便出一道关于【箭头函数】的题,猜猜下面这段代码的输出结果是什么?

let a = 1;
function Foo() {
    let a = 2;
    this.logA = () => {
        console.log(++a)
    }
}
let p = new Foo()
p.logA();    // 输出?
let {logA} = p
logA();  // 输出?
5.6、name属性

因为class目前是构造函数和原型链的语法糖,因此当分析class的时候,可以参考构造函数和原型链。

类的方法是函数,那么类本身是什么呢?

如果仔细阅读过上面的内容,会注意到,类的构造函数和类是全等的。

即:

class Foo {

}
Foo.prototype.constructor === Foo   // true

通过以下方法可以帮助理解类的实质

class Foo {

}
Object.prototype.toString.call(Foo) // "[object Function]"
Foo.constructor === Function    // true

那么,类本身就是函数。那么类的name属性,就是指函数的name属性,因此就是类名。

Foo.name;   // "Foo"
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值