漫谈 JavaScript 里的对象、继承、作用域与其它

工具:Babel 在线编译:https://babeljs.io/repl/

好用的对象字面量与进阶

「一切都是对象」是简洁概念,但是像下面这样创建实例太辛苦。

var person = new Object()

person.firstName = 'Jade'

person.lastName = 'Gu'

person.name = 'Jade Gu'

我们想要更直观的方式,如:

var person = {

firstName: 'Jade',

lastName: 'Gu',

name: 'Jade Gu'

}

如你所见,name属性,无非是firstNamelastName的组合,而上面我却得重复两次,实在不便。

我们需要一个「计算属性」,在需要用到时,它自行根据已有属性计算出结果。

之前的 JavaScript 没有提供便捷的方法,我们得用下面这种冗长做法。

var person = {

firstName: 'Jade',

lastName: 'Gu'

}

Object.defineProperty(person, 'name', {

get: function() {

return [this.firstName, this.lastName].join(' ')

}

})

person.name // Jade Gu

只是写个姓名而已,竟然难以找到编写的舒服姿势,很难受是吧?

ES2015 带了了一些福音。让我们可以这样写:

var person = {

firstName: 'Jade',

lastName: 'Gu',

get name() {

return [this.firstName, this.lastName].join(' ')

}

}

经过 Babel 编译后是这个模样:

'use strict';

var person = Object.defineProperties({

firstName: 'Jade',

lastName: 'Gu'

}, {

name: {

get: function get() {

return [this.firstName, this.lastName].join(' ');

},

configurable: true,

enumerable: true

}

});

如果只写一个人名,那么上面的足够了;但实际上我们需要用到很多人名,每次都写get name,心很累。

我们要封装,对于人名做「最小数据关注量」。比如下面:

function createPerson(firstName, lastName) {

return {

firstName: firstName,

lastName: lastName,

get name() {

return [this.firstName, this.lastName].join(' ')

}

}

}

var person1 = createPerson('Jade', 'Gu')

var person2 = createPerson('Hehe', 'Da')

这个方式叫「工厂模式」。在这个场景中,它有两大不美。其一是createPerson名字冗长,其二是get name每次都会创建一个新的函数。

我们想要「最小打字量」跟「最小内存占用」。下面这种方式,更接近我们的目标:

function Person(firstName, lastName) {

this.firstName = firstName

this.lastName = lastName

}

Object.defineProperty(Person.prototype, 'name', {

get: function() {

return [this.firstName, this.lastName].join(' ')

}

})

var person1 = new Person('Jade', 'Gu')

var person2 = new Person('Hehe', 'Da')

person1.name // "Jade Gu"

person2.name // "Hehe Da"

原型上的计算属性,也能影响到实例。当实例自身没有name属性时,JS 引擎查找原型上有没有,原型有个同名计算属性,被查找时也就启动了计算,然后返回结果。计算 name 的函数只需要一份,放在原型对象上就可以了。省了内存。

这样我们用new取代了createnew Person而不是createPerson,省了创建时的打字量。

但如你所见,在定义时,不够美观,感觉给Person.prototype做特殊处理,而不是很自然的定义Person

name当然是Person很自然要拥有的属性之一,为什么要在定义时要显示地用别的函数(Object.defineProperty)?

所以,我们要用 ES2015 的语法

class Person {

constructor(firstName, lastName) {

this.firstName = firstName

this.lastName = lastName

}

get name() {

return `${this.firstName} ${this.lastName}`

}

}

const person1 = new Person('Jade', 'Gu')

const person2 = new Person('Hehe', 'Da')

这下感受自然得多。再来感受一下python的语法:

class Person(object):

def __init__(self, firstName, lastName):

self.firstName = firstName

self.lastName = lastName

@property

def name(self):

return '%s %s' % (self.firstName, self.lastName)

person1 = Person('Jade', 'Gu')

person2 = Person('Hehe', 'Da')

在 ES2015 出现之前,我觉得 python 的语法很干净。而现在,我更倾向于 ES2015。

在 python 中,只要用装饰符@property,就可以更自然的定义计算属性,将一个 name 方法调用当做属性来使用。

可惜在 ES2015 中,还没有装饰符。然而可期的是,ES2016 可能有。现在用 Babel 也可以书写了。

function readonly(target, name, descriptor) {

return {

get: descriptor.value

}

}

class Person {

constructor(firstName, lastName) {

this.firstName = firstName

this.lastName = lastName

}

@readonly

name() {

return `${this.firstName} ${this.lastName}`

}

}

const person1 = new Person('Jade', 'Gu')

const person2 = new Person('Hehe', 'Da')

好吧,看起来还不如get name。在目前这个简单场景内没有优势,但更复杂的情况下,装饰符能很好地分离复杂度,将那些固定的、与业务无关的特性处理工作,移出「定义点」,让「定义点」代码更干净与直观。

旨在共享数据的原型与作用域

如前一节所示,在 ES2015 之前,JavaScript 里定义最简单的人名也像「醉汉走路」。在七扭八歪的代码里,才终于达到了目标。

我们的目标是:更小打字量,更小内存消耗。

这意味着,我们要共享很多东西,才能抑制暴涨。

「继承与组合」是共享的两大方式。

共享的原理是:「世界终究是孙子们的」。不信,你看:

function grandpa() {

var grandpa_money = 1000000

function father() {

var father_money = 500000

function me() {

var my_money = 300000

function child() {

var child_money = 100000

function grandson() {

var grandson_money = 100

grandson_money += child_money

grandson_money += my_money

grandson_money += father_money

grandson_money += grandpa_money

child_money = my_money = father_money = grandpa_money = 0

child = me = father = grandpa = null

console.log(grandson_money)

console.log(child_money, my_money, father_money, grandpa_money)

console.log(child, me, father, grandpa)

}

grandson()

}

child()

}

me()

}

father()

}

grandpa()

祖父拥有1000000,创造了父亲;

父亲拥有500000,创造了我;

我拥有300000,创造了儿子;

儿子拥有100000,创造了孙子;

孙子拥有100,把祖上的前都败光,并且欺师灭祖。

这就是 JavaScript 里的作用域链;根据创建时间与环境,确定可支配变量的范围。

正如我们自诩拥有的「五千年文化遗产」一样,最后创建的,最里层的grandson可支配祖上所有变量。

这样设计的好处是,如果我需要的数据已经在内存中,那么就不必重复创建。然后两个平级的函数之间无法互相访问对方的私有变量,就做到了「数据隐私控制」。

上面是作用域链层面的数据共享,下面看看原型链层面的数据共享。

var Ancestors = {

'人的成长': '生老病死',

'地球位置': '世界中心',

'地球形状': '天圆地方',

'人类起源': '上帝造人'

}

var SomeoneA = Object.create(Ancestors)

Object.assign(SomeoneA, {

'地球位置': '太阳系中心',

'地球形状': '地球是圆的',

'人类起源': '物种演化'

})

var SomeoneB = Object.create(SomeoneA)

Object.assign(SomeoneB, {

'地球位置': 'https://zh.wikipedia.org/wiki/%E5%9C%B0%E7%90%83%E5%9C%A8%E5%AE%87%E5%AE%99%E4%B8%AD%E7%9A%84%E4%BD%8D%E7%BD%AE',

'地球形状': 'https://www.google.com/search?q=%E5%9C%B0%E7%90%83%E5%BD%A2%E7%8A%B6&es_sm=122&tbm=isch&tbo=u&source=univ&sa=X&ved=0CB4QsARqFQoTCJv1iZvb4cYCFRIakgod5XAAyg&biw=1680&bih=912',

'人类起源': '裸猿/走出非洲大草原'

})

原型的数据共享方式,类似于科学知识的发展。

你没有新发现,你所有知识都来自于前人的研究成果。

你有新发现,就以你的新发现为准。你推翻(删除)了你的新发现,还是以祖先的为准。

你访问SomeoneB能得到最新的数据,其中「人的成长」是在这里是颠扑不破的数据,还是沿用祖先的。

你要获取某一时期的数据,访问那个时期的对象即可。如此,获取数据以及数据之间的关系,就很便利了。

然而,知识可以共享与迭代,技能呢?

我如何在 JavaScript 中,写出简易的物种演化模型?

演化是一点点的积累,不是覆盖,不能每个物种都重新发明所有技能;所以,class就显得很重要

//只会说呵呵哒的「人」

class H {

constructor() {

this.type = 'h'

}

say() {

console.log('呵呵哒')

}

}

class Hu extends H {

constructor() {

super()

this.type = 'hu' //覆盖你

},

think() {

return Math.random() > 0.5 ? '么么哒' : null

}

say() {

//有想法说想法,没想法呵呵哒

if (!this.think) {

super.say()

} else {

console.log(this.mind)

}

}

}

class Hum extends Hu {

constructor() {

super()

this.type = 'hum'

this.memory = {}

}

remember(information) {

this.memory[new Date().getTime()] = information

}

think(key) {

return key ? this.memory[key] : super.think()

}

}

class Human extends Hum {

constructor() {

super()

this.type = 'human'

}

think(key) {

var result = super.think(key)

return result.includes('Miss Right') ? 'I Love You' : result

}

}

技能的传承跟数据的传承,性质不同。

我们可能不再需要祖先的错误知识,但还是无法离开祖先遗传下来的呼吸能力。

结语

嗯,这就是我眼中的 Javascript ,一副醉汉模样,好在有 Babel ,终于快清醒过来了。

这一醉,就是20年。

以上是 漫谈 JavaScript 里的对象、继承、作用域与其它 的全部内容, 来源链接: utcz.com/z/264736.html

回到顶部