我的首个微信小程序尝试,以及一点看法。

小程序开发

微信小程序的代码架构还是比较清晰的,根目录下 app.js 用于注册小程序,app.json 是小程序的配置文件, app.wxss 是样式表文件。

简单起见,我就选择了MovieRank这个项目,一是后端 API 和前端结构都是现成的。

在 pages 文件夹内创建 index 文件夹用于起始页面(也就只有这一个页面),然后在 app.json 的 pages 中注册页面"pages": ["pages/index/index"],index.wxml 类似于 html 文档, index.js 可通过 Page 函数注册页面,该函数接收一个对象参数,指定页面初始数据、生命周期函数、事件处理函数等。 index.wxss 是样式表文件,兼容大部分 CSS 语法。

由于小程序的 API 比较类似 Vue.js,所以整个开发过程配合查看官方 API 很快就完成了,这是结果:

主页面:

index

条件选择:

picker

初试感受

由于只是初次尝试,大部分功能还未熟悉,只谈一谈初试的感受:

  • 结构和逻辑清晰,可以集中精力到逻辑层面的开发。

  • 不支持其他框架扩展。这一点可能也是好处,可以统一小程序兼容性与用户体验。

  • 不支持个人开发者注册,不知道后续会不会开放。

  • 一点不足:目前的主流前端框架(Vue.js, AngularJS)大多使用双向数据绑定,小程序是单向数据绑定,通过事件侦听来处理视图层的数据改变,因此可能需要编写很多事件处理函数。

小程序(Web App)与 Native App

小程序正式发布当天,很多人宣称可以卸载本地 App 了,又有人说小程序不可能取代 App。小程序的优点是无需下载,随开随用,不占内存,其本质是一个 Web App。而关于Web App,Google 多年前就有 Chrome App,甚至还有主要运行 Web App 的操作系统 Chrome OS。Chrome Store 上也有成千上万的 App,可在去年8月 Google 却宣布将在接下来两年停止对 Chrome App 的支持,鼓励开发者将 Chrome App 搬迁到 Web 上。但这个停止支持其实是随着技术进步,标准的统一,Google 鼓励去开发跨浏览器、兼容性更好的 Web App:Progressive Web Apps

Progressive Web Apps 是结合了 web 和 原生应用中最好功能的一种体验。对于首次访问的用户它是非常有利的, 用户可以直接在浏览器中进行访问,不需要安装应用。随着时间的推移当用户渐渐地和应用建立了联系,它将变得越来越强大。它能够快速地加载,即使在比较糟糕的网络环境下,能够推送相关消息, 也可以像原生应用那样添加至主屏,能够有全屏浏览的体验。
- 你的首个 Progressive Web App

未来如何不敢妄言,但小程序以及 Progressive Web App 肯定会促进 Web 技术的发展,Web App 也肯定会有越来越好的体验。

ps:源码

参考


由于之前在Comodo申请的SSl证书马上就要到期,刚好看到现在很多网站已经用上了Let's Encrypt的证书,了解了下配置和更新都十分简单,因此决定本站也转向Let's Encrypt。简单记录一下配置过程(环境:CentOS 7 + nginx)。

1.开启EPEL

使用Certbot配置证书,首先要开启EPEL(Extra Packages for Enterprise Linux)。

sudo yum install epel-release

2.安装Certbot

sudo yum install certbot

3.生成及配置证书

预先工作

在证书生成过程中,Certbot会在网站根目录生成一个随机文件(.well-known/acme-challenge/some_random.html),Certbot的服务器会访问地址imliyan.com/.well-known/acme-challenge/some_random.html用于验证,但由于网站是使用 Django 开发并用 uwsgi + nginx 部署的,除静态文件外的 url 都被转到 Django 处理,因此这一步会返回 404,解决办法可以在 Django 的 urls.py 中进行配置或配置 nginx,此处选择配置 nginx。

server {
    location /.well-known/acme-challenge {
        alias /path/to/imliyan.com/.well-known/acme-challenge;
    }
}

生成证书

certbot certonly --webroot -w /path/to/imliyan.com -d imliyan.com -d www.imliyan.com

上述命令会为imliyan.comwww.imliyan.com生成一个单独的证书。证书存储于 /etc/letsencrypt/live/imliyan.com/fullchain.pem

nginx配置

server {
    listen 443 ssl;
    server_name www.imliyan.com imliyan.com;

    ssl_certificate /etc/letsencrypt/live/imliyan.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/imliyan.com/privkey.pem;
    ssl_trusted_certificate /etc/letsencrypt/live/imliyan.com/chain.pem;
}

重启 nginx

nginx -s reload

到此配置完成。整个过程相比 Comodo 手工生成证书签名请求(CSR)、生成证书、上传证书、配置要简单不少,关键是到期更新的话也十分简单。

Django使用模型(model)来定义数据,模型包含数据的字段以及行为信息,通常可以认为每个模型对应于一个数据库表。Django的模型系统实现了数据库的常见关系(一对一、一对多、多对多),本文主要介绍这三种关系模型的使用。

一对一关系

Django使用OneToOneField处理一对一关系。我们大学和地点来进行一对一关系的演示。一个地点可以是一所大学。

首先定义模型:

from django.db import models

class Place(models.Model):
    name = models.CharField(max_length=50)
    address = models.CharField(max_length=50)

    def __str__(self):
        return self.name

class University (models.Model):
    name = models.CharField(max_length=50)
    place = models.OneToOneField(
        Place,
        primary_key = True
    )

    def __str__(self):
        return self.name

之后就可以用Django的API来进行模型操作了:

> python manage.py shell
>>> from myapp.models import Place, University

# 创建地点
>>> place1 = Place(name="Peking University",
        address="No.5 Yiheyuan Road Haidian District, Beijing")
>>> place2 = Place(name="Tsinghua University",
        address="No.30 Shuangqing Road Haidian District, Beijing")
>>> place3 = Place(name="Somewhere",
        address="Someroad Somecity")
>>> place1.save()
>>> place2.save()
>>> place3.save()

# 创建大学
>>> university1 = University(name="Peking University", place=place1)
>>> university2 = University(name="Tsinghua University", place=place2)
>>> university1.save()
>>> university2.save()

# 地点和大学模型可以相互访问
>>> place1.university
<University: Peking University>
>>> university1.place
<Place: Peking University>
>>> place2.university.name
'Tsinghua University'
>>> university2.place.address
'No.30 Shuangqing Road Haidian District, Beijing'

# 地点3目前还没有大学
>>> hasattr(place3, 'university')
False
# 使用赋值语句为清华大学设置新地点
>>> university2.place = place3
>>> university2.save()
>>> hasattr(place3, 'university')
True
>>> place3.university
<University: Tsinghua University>

# 注意place为University的主键,因此上述操作创建了一个新的大学对象
>>> University.objects.all()
<QuerySet [<University: Peking University>, <University: Tsinghua University>, <University: Tsinghua University>]>

一对多关系

Django使用ForeignKey来处理一对多关系,我们用大学和学生的关系来进行演示:一个学校可以有多个学生。

定义学生模型

class Student(models.Model):
    university = models.ForeignKey(University)
    name = models.CharField(max_length=50)

    def __str__(self):
        return self.name

模型操作:

>>> from myapp.models import University, Student

# 创建学生
>>> pku = University.objects.get(name="Peking University")
>>> zhangsan = Student(name="Zhang San", university=pku)
>>> lisi = Student(name="Li Si", university=pku)
>>> zhangsan.save()
>>> lisi.save()

# 查看张三的学校信息
>>> zhangsan.university
<University: Peking University>
>>> lisi.university.place.address
'No.5 Yiheyuan Road Haidian District, Beijing'

# 查看pku的学生信息
>>> pku.student_set.all()
<QuerySet [<Student: Zhang San>, <Student: Li Si>]>
>>> pku.student_set.get(name="Li Si")
<Student: Li Si>

# 创建一个没有学校的学生
>>> wangwu = Student(name="Wang Wu")
>>> wangwu.save()
>>> wangwu.university
Traceback (most recent call last):
  File "<console>", line 1, in <module>
 ...
django.db.models.fields.related_descriptors.RelatedObjectDoesNotExist: Student has no university.

# 添加到pku
>>> pku.student_set.add(wangwu)
>>> wangwu.university
<University: Peking University>
>>> pku.student_set.all()
<QuerySet [<Student: Zhang San>, <Student: Li Si>, <Student: Wang Wu>]>

# 查看pku有几个学生
>>> pku.student_set.count()
3

# 查看pku名字以"Li"开头的学生
>>> pku.student_set.filter(name__startswith="Li")
<QuerySet [<Student: Li Si>]>

# 查看学校名字为"Peking University"的学生
>>> Student.objects.filter(university__name="Peking University")
<QuerySet [<Student: Zhang San>, <Student: Li Si>, <Student: Wang Wu>]>

多对多关系

Django使用ManyToManyField来处理多对多关系,我们用学生和课程的关系来进行演示:一个学生可以选择多个课程,每个课程可以有多个学生来上课。

定义课程模型

class Course(models.Model):
    student = models.ManyToManyField(Student)
    name = models.CharField(max_length=50)

    def __str_(self):
        return self.name

模型操作

>>> from myapp.models import University, Student, Course
>>> hm = Course(name="Higher Mathematics")
>>> ms = Course(name="Mathematical Statistics")
>>> hm.save()
>>> ms.save()
>>> pku = University.objects.get(name="Peking University")
>>> zhangsan = Student.objects.get(name="Zhang San")
>>> lisi = Student.objects.get(name="Li Si")
>>> wangwu = Student.objects.get(name="Wang Wu")

# lisi目前还没有选择课程
>>> lisi.course_set.all()
<QuerySet []>

# 高等数学目前还没有学生选择
>>> hm.student.all()
<QuerySet []>

# 我们给lisi选择两门课程
>>> lisi.course_set.add(hm, ms)
>>> lisi.course_set.all()
<QuerySet [<Course: Higher Mathematics>, <Course: Mathematical Statistics>]>
>>> ms.student.all()
<QuerySet [<Student: Li Si>]>

# 我们再给数理统计课程添加两名学生
>>> ms.student.add(zhangsan, wangwu)
>>> ms.student.all()
<QuerySet [<Student: Zhang San>, <Student: Li Si>, <Student: Wang Wu>]>

# 直接给高等数学再创建并添加一名学生
>>> zhaoliu = hm.student.create(name="Zhao Liu", university=pku)
>>> hm.student.all()
<QuerySet [<Student: Li Si>, <Student: Zhao Liu>]>

# 交叉查询
# 查询选择了高等数学的学生
>>> Student.objects.filter(course__name="Higher Mathematics")
<QuerySet [<Student: Li Si>, <Student: Zhao Liu>]>

# 查询有李四参与的课程
>>> Course.objects.filter(student__name="Li Si")
<QuerySet [<Course: Higher Mathematics>, <Course: Mathematical Statistics>]>

# 张三退掉数理统计
>>> zhangsan.course_set.remove(ms)
>>> zhangsan.course_set.all()
<QuerySet []>

# 从高等数学课程中删除赵六
>>> hm.student.remove(zhaoliu)
>>> hm.student.all()
<QuerySet [<Student: Li Si>]>

参考:

简述JavaScript对象与继承

JavaScript是基于原型的语言,每一个对象都有一个原型对象。通过构造器函数实例化的对象可以通过浏览器实现的__proto__接口访问其原型对象(ECMA官方实现为Object.getPrototypeOf(obj)),此对象的构造器函数同样有一个属性 prototype指向此原型对象。

当查找对象属性时,会首先在对象上查找,如果找到则返回,未找到则查找对象的原型对象、原型对象的原型对象……一直到查找到为止或者某对象的原型对象为null

如果要实现继承,可以指定子对象的原型是父对象的一个实例,也即:

SubType.prototype = new SuperType()

对象与继承的基础知识本文不再详述,接下来对比构造函数与class两种语法格式创建对象的特点。

使用构造器函数

function Dog(name) {
    this.name = name
    this.bark = function() {
        console.log(this.name + ' Wang!')
    }
}
Dog.prototype.walk = function() {
    console.log(this.name + ' Walking!')
}

var d = new Dog('Tom')

// name, bark 是对象 d 的属性
d.hasOwnProperty('name')
// true
d.hasOwnProperty('bark')
// true

// walk 不是对象 d 的属性
d.hasOwnProperty('walk')
// false

d.__proto__.hasOwnProperty('walk')
// true
d.__proto__ === Dog.prototype
// true
d.walk()
// 'Tom Walking!'

实现继承

function Doggy(name, age) {
    Dog.call(this, name)
    this.age = age
}
var dg = new Doggy('Doggy', 3)

dg.hasOwnProperty('name')
// true
dg.hasOwnProperty('bark')
// true

// 我们虽然在 Doggy 的上下文调用了 Dog 构造函数,
// 但这只是一个新的构造函数, 我们还未实现继承
// 因为 Doggy 的 prototype 未定义,Doggy 未继承 Dog
dg.walk()
// TypeError: dg.walk is not a function

// 指定原型实现继承
Doggy.prototype = Object.create(Dog.prototype)
var dg = new Doggy('Doggy', 3)
dg.walk()
// 'Doggy Walking!'

// dg原型的原型是 d 的原型
// dg 的原型是 Dog 的实例
dg.__proto__.__proto__ === d.__proto__
// true
dg.__proto__ instanceof Dog
// true

ECMAScript 6 之 class

class 在ECMAScript 6中引进,但这只是一个语法糖,JavaScript仍是一个基于原型而不是基于类的语言。

class Dog {
    constructor(name) {
        this.name = name
    }
    bark() {
        console.log(this.name + ' Wang!')
    }
}

class Doggy extends Dog {}

// Dog 其实是一个 function 实例
typeof Dog
// 'function'

var d = new Dog('Tom')
var dg = new Doggy('Doggy')

d.hasOwnProperty('name')
// true

// d 自身没有 bark 属性,是在其原型上查找到的
d.hasOwnProperty('bark')
// false
d.__proto__.hasOwnProperty('bark')
// true
d.__proto__ === Dog.prototype
// true

dg.hasOwnProperty('name')
// true
dg.hasOwnProperty('bark')
// false
dg.__proto__.hasOwnProperty('bark')
// false

// ***********************
//
// dg 对象的原型是 Dog 的实例
// dg 原型的原型就是d的原型
//
// ***********************
dg.__proto__ instanceof Dog
// true
dg.__proto__.__proto__ === d.__proto__
// true

两种构造方法对比可见,其内部实现是一致的, 但是class声明更加清晰明确,特别是对于熟悉其他基于类的语言的同学来说更容易理解。但必须要明白的是,这只是一个语法糖,JavaScript 仍是一个基于原型的语言。

参考


There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
——the Zen of Python

Python提倡用一种,而且最好是只有一种方法来完成一件事。可是在字符串格式化方面Python好像没有做到这一点。除了基本的%-formatting格式,还有str.format()格式、以及string.Template。根据对Python标准库的统计,目前string.Template的使用屈指可数,str.format()获得广泛使用,但是我们来与其他几种语言的字符串格式化对比一下:

已知name = 'Tom',我们如何打印出字符串'My name is Tom.'

Ruby:

puts 'My name is #{name}.'

JavaScript(ECMAScript 2015):

console.log(`My name is ${name}.`)

Python:

print('My name is {name}.'.format(name = name))

# 即便是简化的版本
print('My name is {}.'.format(name))

可以看出Python明显还不够简洁,于是,随着Python3.6版本在上周正式发布,Python提供了一种字符串格式化语法——'f-strings'。

f-strings

要使用f-strings,只需在字符串前加上f,语法格式如下:

f ' <text> { <expression> <optional !s, !r, or !a> <optional : format specifier> } <text> ... '

基本用法

>>> name = "Tom"
>>> age = 3
>>> f"His name is {name}, he's {age} years old."
>>> "His name is Tom, he's 3 years old."

支持表达式

# 数学运算
>>> f'He will be { age+1 } years old next year.'
>>> 'He will be 4 years old next year.'

# 对象操作
>>> spurs = {"Guard": "Parker", "Forward": "Duncan"}
>>> f"The {len(spurs)} players are: {spurs['Guard']} the guard, and {spurs['Forward']} the forward."
>>> 'The 2 players are: Parker the guard, and Duncan the forward.'

>>> f'Numbers from 1-10 are {[_ for _ in range(1, 11)]}'
>>> 'Numbers from 1-10 are [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]'

排版格式

>>> def show_players():
    print(f"{'Position':^10}{'Name':^10}")
    for player in spurs:
        print(f"{player:^10}{spurs[player]:^10}")
>>> show_players()
 Position    Name   
  Guard     Parker  
 Forward    Duncan

数字操作

# 小数精度
>>> PI = 3.141592653
>>> f"Pi is {PI:.2f}"
>>> 'Pi is 3.14'

# 进制转换
>>> f'int: 31, hex: {31:x}, oct: {31:o}'
'int: 31, hex: 1f, oct: 37'

与原始字符串联合使用

>>> fr'hello\nworld'
'hello\\nworld'

注意事项

{}内不能包含反斜杠\

f'His name is {\'Tom\'}'
SyntaxError: f-string expression part cannot include a backslash

# 而应该使用不同的引号,或使用三引号。
>>> f"His name is {'Tom'}"
'His name is Tom'

不能与'u'联合使用

'u'是为了与Python2.7兼容的,而Python2.7不会支持f-strings,因此与'u'联合使用不会有任何效果。

如何插入大括号?

>>> f"{{ {10 * 8} }}"
'{ 80 }'
>>> f"{{ 10 * 8 }}"
'{ 10 * 8 }'

str.format()的一点不同

使用str.format(),非数字索引将自动转化为字符串,而f-strings则不会。

>>> "Guard is {spurs[Guard]}".format(spurs=spurs)
'Guard is Parker'

>>> f"Guard is {spurs[Guard]}"
Traceback (most recent call last):
  File "<pyshell#34>", line 1, in <module>
    f"Guard is {spurs[Guard]}"
NameError: name 'Guard' is not defined

>>> f"Guard is {spurs['Guard']}"
'Guard is Parker'

参考: