• Home

iPhone 6和6Plus屏幕、UI设计和适配那些事

且看iPhone几个系列的屏幕数据:

 iPhone 4*iPhone 5*iPhone 6iPhone 6P
物理大小 - 对角(inch)3.544.75.5
物理大小 - 宽高(inch)1.94*2.911.96*3.482.3*4.12.7*4.79
PPI329.65325.97325.61400.53
物理分辨率(px)640*960640*1136750*13341080*1920
逻辑分辨率(pt)320*480320*568375*667414*736
渲染分辨率(px)640*960640*1136750*13341242*2208
像素/点比率2222.6

首先要看到的事实是6P的ppi变大了,也就是像素更小了。如果还是按1:2的点像比,那么这块1080*1920像素分辨率的屏幕应该有540*960的点分辨率。但实际计算用到点分辨率却是414*736,点像比是1:2.6,不到1:3。
那么问题来了,为什么苹果不沿用326ppi左右屏幕,而要用401ppi的屏幕?为什么6P的点分辨率是414*736?

关于这些问题可以参考知乎iPhone 6 Plus的逻辑分辨率为什么是414×736?
大概就是保证:
1.6P屏幕必须6大,5.5英寸为前提;
2.显示更多内容,所以点分辨率必须大于375*667;
3.ppi不能比之前低;
4.同样逻辑单位(点)的内容,如字体、按钮物理尺寸不能比之前小,否者会造成视觉、操作不便。
5.在现有工艺可达到。
所以最终401ppi、414*736、1080*1920、1:2.6的配置更像是一种折中的非完美方案。

问题又来了,为什么不是326ppi、880*1560(880≈2.7”*326ppi,1560≈4.79”*326ppi)、440*780、1:2这种尺寸呢?

这样还可以继续用2x图。这里就略过不研究了。

如有2.6对于开发、设计来说都不是一个特好的数字,所以苹果把它约等了一下变成3,又搞了个渲染分辨率的概念。
414*736的3倍正是1242*2208,在这个分辨率计算界面的样式,然后把最终的画面下采样(downsampling)缩小1.15显示到1080*1920上。渲染分辨率还用在未专门适配4.7英寸的iPhone 6的时,把4英寸的640*1136画面上采样(upsampling)放大1.171875倍显示在750*1334屏幕上,当然会看起来比较模糊。采用3的话以后如果采用461ppi屏幕也更容易兼容。

iPhone 6 plus downsampingiphone 6 upsampling

更全更详细的图示请参见:The Ultimate Guide To iPhone Resolutions

PPI计算和常见手机PPI信息:https://www.sven.de/dpi/

以主屏幕分辨率的app图标为例,大小为60pt*60pt,为什么@3x下icon的尺寸180*180就是正确的呢?在@2x下120*120的icon,物理尺寸是0.36”≈120px*1.96”/640px,而@3x下180*180的icon物理尺寸是0.39≈(180px/11.5)*2.7”/1080px。可见他们的物理尺寸基本一致。

对于6P,@2x素材的大小乘以1.5便是@3x图的大小。

对于iPhone 5系,屏幕只是变长了,垂直方向上的适配很简单。然后对于6和6p来说,不仅边长变宽,点像比率也变大了。那么在设计iPhone UI时就不再像以前一样了。应该结合使用point、autolayout的“相对”布局,什么时候该用固定point大小,什么时候该用相对尺寸,具体并没有可套用的准则。

比如主屏icon图标保证尺寸是60point,横向平分间隔;相册里每行4张缩略图,4等分,没有具体point大小;nav bar是同一Point大小;tab bar图标统一point大小,间隔均分。

在出设计图给程序员时的标注也不再全部是绝对的像素或pt,像间隔应该表明是横向均分。

用markman标注

在设计时应该以哪个iPhone为参考呢?画布尺寸为多大呢?
画布应该以iPhone4的点分辨率320px*480px,放大3倍,即960px*1440px为标准。这样既保证了UI对最小屏幕的绝对适配,也方便了@3x素材的导出。
在横向上,6和6P使空白区域拉伸即可。纵向可以结合留白、放大、使用滚动。

 

在编程实现应该使用Autolayout,摒弃过去的绝对pt布局。

使用代码进行布局时可以使用一些对原生NSLayoutConstraints的封装简化库,减少臃肿,提高代码可读性,加快速度。

MasonryPureLayoutKeepLayout

本人使用 PureLayout,虽然语法没 Masonry 那么简单,但是 bug 少。

 

修复cocoapods

在拉下松爷更新的Podfile后,尝试用pod update更新,结果出现下面错误

$pod update
Update all pods
Analyzing dependencies
[!] Unable to satisfy the following requirements:

- `JSQMessagesViewController (~> 6.1.0)` required by `Podfile`

没什么头绪,尝试查一下版本

$pod list | grep JSQM
  JSQMessagesViewController 6.0.0

JSQMessagesViewController主页明明标了已经是6.1.1,是不是版本库有本地缓存,在pod --help加google下,发现命令pod repo update,果断运行一下,卡住,加上–verbose

$pod repo update --verbose
  $ /usr/bin/git rev-parse  >/dev/null 2>&1

Updating spec repo `master`
  $ /usr/bin/git pull --ff-only
  error: Your local changes to the following files would be overwritten by merge:
  	CocoaPods-version.yml
  	Specs/AdMobMediationAdapterMMedia/1.5.0/AdMobMediationAdapterMMedia.podspec.json
  Please, commit your changes or stash them before you can merge.
  error: The following untracked working tree files would be overwritten by merge:
  	Specs/AAShareBubbles/1.1.0/AAShareBubbles.podspec.json
  	...[略去n行]
  	Specs/AQSEvent/0.2.0/AQSE
  Aborting
  Updating da90008..86016e3

[!] CocoaPods was not able to update the `master` repo. If this is an unexpected issue and persists you can inspect it running `pod repo update --verbose`

果然本地有缓存,还是git管理的,更新时冲突,google了一下,找到相关资料:
1.http://stackoverflow.com/questions/19477178/cocoapods-error-during-pod-update
2.http://blog.cocoapods.org/Repairing-Our-Broken-Specs-Repository/

解决方法:删除本地缓存,重新setup

$rm -fr ~/.cocoapods/repos/master
$pod setup

centos 6.5安装git

#yum install curl-devel expat-devel gettext-devel openssl-devel zlib-devel perl-devel
#wget https://www.kernel.org/pub/software/scm/git/git-2.2.0.tar.gz
#tar -zxf git-2.2.0.tar.gz 
#cd git-2.2.0
#make prefix=/usr/local all
#make prefix=/usr/local install

如果出错下面错误

/usr/bin/perl Makefile.PL PREFIX='/usr/local' INSTALL_BASE='' --localedir='/usr/local/share/locale'
Can't locate ExtUtils/MakeMaker.pm in @INC (@INC contains: /usr/local/lib64/perl5 /usr/local/share/perl5 /usr/lib64/perl5/vendor_perl /usr/share/perl5/vendor_perl /usr/lib64/perl5 /usr/share/perl5 .) at Makefile.PL line 3.
BEGIN failed--compilation aborted at Makefile.PL line 3.
make[1]: *** [perl.mak] Error 2
make: *** [perl/perl.mak] Error 2

就是没装perl-devel导致

python MySQLdb例子

以下代码使用了torndb(torndb是对mysqldb的简单封装,调用和使用cursor一致)
mysqldb如果查询时传入的参数是值序列,则sql的paramstyle必须是format,即%s作为占位符;
如果传入的参数是键值序列,则sql的paramstyle必须是pyformat,即%(xxx)s作为占位符;

1.count

    def get_all_topics_count_by_node_slug(self, node_slug):
        sql = 'SELECT COUNT(0) FROM topic LEFT JOIN node ON topic.node_id = node.id WHERE node.slug = %s'
        return self.db.get(sql, node_slug)['COUNT(0)']

2.select(分页)

    def get_all_topics_by_node_slug(self, node_slug, current_page=1, page_size=36, ):
        sql = '''SELECT topic.*,
                author_user.username as author_username,
                author_user.nickname as author_nickname,
                author_user.avatar as author_avatar,
                author_user.uid as author_uid,
                author_user.reputation as author_reputation,
                node.name as node_name,
                node.slug as node_slug,
                last_replied_user.username as last_replied_username,
                last_replied_user.nickname as last_replied_nickname
                FROM topic
                LEFT JOIN user AS author_user ON topic.author_id = author_user.uid
                LEFT JOIN node ON topic.node_id = node.id
                LEFT JOIN user AS last_replied_user ON topic.last_replied_by = last_replied_user.uid
                WHERE node.slug = %s
                ORDER BY last_touched DESC, created DESC, last_replied_time DESC, id DESC
                LIMIT %s, %s'''

        total_count = self.get_all_topics_count_by_node_slug(node_slug)
        total_page = int(math.ceil(total_count / float(page_size)))
        current_page = current_page if current_page <= total_page else total_page
        current_page = current_page if current_page >= 1 else 1
        previous_page = current_page - 1 if current_page > 1 else 1
        next_page = current_page + 1 if current_page < total_page else total_page

        result = {
            "list": self.db.query(sql, node_slug, (current_page-1)*page_size, page_size),
            "page": {
                "prev": previous_page,
                "next": next_page,
                "current": current_page,
                "pages": total_page,
                "total": total_count,
                "size": page_size
            }
        }
        return result

3.insert(任意字段)

    def add_new_topic(self, topic_info):
        sql = 'INSERT INTO topic ('
        sql += ', '.join(topic_info.keys())
        sql += ') VALUES ('
        sql += ', '.join(['%s'] * len(topic_info.keys()))
        sql += ')'
        return self.db.insert(sql, *topic_info.itervalues())

4.update(任意字段,pyformat)

    def update_topic_by_topic_id(self, topic_id, topic_info):
        sql = 'UPDATE topic SET '
        sql += ', '.join(['%s = %%(%s)s' % (k, k) for k in topic_info.keys()])
        sql += ' WHERE id = %(id)s'
        print sql
        return self.db.update(sql, id=topic_id, **topic_info)

python db api正确使用方式

很多人在用Python DB API时会用以下方式拼接sql

cmd = "update people set name='%s' where id='%s'" % (name, id) curs.execute(cmd)

然后调用cursor.execute执行,这样容易产生sql注入

正确的方法是使用占位符语法

cmd = "update people set name=%s where id=%s" 
curs.execute(cmd, (name, id))

execute语句在执行时会先对元组(name, id)的值转为字符串,进行转义,

不同的数据库支持不同占位符语法,常见占位符包括以下:
1.’qmark’ Question mark style, e.g. ‘…WHERE name=?’
2.’numeric’ Numeric, positional style, e.g. ‘…WHERE name=:1’ ‘named’
3.’named’ Named style, e.g. ‘…WHERE name=:name’
4.’format’ ANSI C printf format codes, e.g. ‘…WHERE name=%s’
5.’pyformat’ Python extended format codes, e.g. ‘…WHERE name=%(name)s’
注意pyformat后的s

常见数据库python链接库实现的默认paramstyle

>>>import MySQLdb; 
print MySQLdb.paramstyle 
format 
>>> import psycopg2; 
>>>print psycopg2.paramstyle 
pyformat
>>> import sqlite3; 
print sqlite3.paramstyle 
qmark

如果你在使用MySQL 或 PostgreSQL, 可以用%s(即使是数字或者非字符)

更全的关于python防止sql注入的资料下载sqlinjection-120205200846-phpapp01

检查安装的Python是否支持UCS-4

>>> import sys
>>> print sys.maxunicode

当用--enable-unicode=ucs4编译安装时,输出的是1114111;
当用--enable-unicode=ucs2编译安装时,输出的是65535.

吐槽j2ee&maven

maven作为一个j2ee事实上的标准,被大部分的开源项目采用,但是找一个库的坐标是真新费劲。
j2ee开源项目为什么不学习python开源项目,在官方主页把坐标明显的标出来啊!!!!
在http://search.maven.org和http://www.mvnrepository.com/搜出来的各种重复!不能做个官方认证吗?
以jstl为例,这么写行

        
            jstl
            jstl
            1.2
        

这么写也行

        
            javax.servlet
            jstl
            1.2
        

1.1还要加个taglibs:standard:1.1.2;
以jackson为例,2.x和1.x连groupId都换了,也是醉了。

J2EE真心混乱啊。

osx升级到10.9以上homebrew不能用

想用homebrew装个maven结果

$ brew install maven
usr/local/bin/brew: /usr/local/Library/brew.rb: /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby: bad interpreter: No such file or directory
/usr/local/bin/brew: line 23: /usr/local/Library/brew.rb: Undefined error: 0

原因是系统升级后ruby的版本升级到到了2.0,而/usr/local/Library/brew.rb里写死了用1.8版本运行brew

#!/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby -W0
# encoding: UTF-8

在osx 10.10 Yosemite里尝试直接修改1.8为2.0结果don’t work!
在github里找到解决方法:

cd /usr/local
git fetch origin
git reset --hard origin/master

brew又可以用啦

chrome删除重复书签

google书签在自动重复后会莫名其妙的出现以下重复,同步机制和算法真是不敢恭维,比svn、git之类的diff弱多了。

去掉重复书签可以用Chrome扩展应用如SuperSorter、Bookmark Sentry。
SuperSorter谨慎使用,虽然可以去重,但是不能合并同名文件夹,会打乱文件夹排序。
Bookmark Sentry已经在Chrome Web Store找不到了。

或者可以使用delicious来收藏或者管理,同样有chrome插件。

最后提醒:使用这些插件扫描你的书签可能会泄露隐私。

javascript快速入门

以下内容来自learnxinyminutes.com

Javascript于1995年由网景公司的Brendan Eich发明。 最初发明的目的是作为一个简单的网站脚本语言,来作为 复杂网站应用java的补充。但由于javascript和网站结合度很高 所以javascript逐渐变得比java在前端更为流行了。
JavaScript 不仅仅只可以用于浏览器, 也可用于 Node.js 等后台环境。


// 注释方式和C很像,这是单行注释
/* 这是多行
   注释 */

// 语句可以以分号结束
doStuff();

// ... 但是分号也可以省略,每当遇到一个新行时,分号会自动插入
doStuff()

// 我们在这里会去掉分号,但是否添加最后的分号取决于你个人的习惯
// 及你所在团队的编程风格

///////////////////////////////////
// 1. 数字、字符串与操作符

// Javascript 只有一种数字类型 (即 64位 IEEE 754 双精度浮点).
3 // = 3
1.5 // = 1.5

// 所有基本的算数运算
1 + 1 // = 2
8 - 1 // = 7
10 * 2 // = 20
35 / 5 // = 7

// 包括无法整除的除法
5 / 2 // = 2.5

// 位运算也和其他语言一样。当你对浮点数进行位运算时,
// 浮点数会转换为至多 32 位的无符号整数
1 << 2 // = 4

// 括号可以决定优先级
(1 + 3) * 2 // = 8

// 有三种非数字的数字类型
Infinity //  1/0 的结果
-Infinity // -1/0 的结果
NaN // 0/0 的结果

// 也有布尔值
true
false

// 可以通过单引号或双引号来构造字符串
'abc'
"Hello, world"

// 用!来取非
!true // = false
!false // = true

// 相等 ==
1 == 1 // = true
2 == 1 // = false

// 不等 !=
1 != 1 // = false
2 != 1 // = true

// 更多的比较操作符 
1 < 10 // = true
1 > 10 // = false
2 <= 2 // = true
2 >= 2 // = true

// 字符串用+连接
"Hello " + "world!" // = "Hello world!"

// 字符串也可以用 < 、> 来比较
"a" < "b" // = true

// 比较时会进行类型转换...
"5" == 5 // = true

// ...除非你是用 ===
"5" === 5 // = false

// 你可以用charAt来得到字符串中的字符
"This is a string".charAt(0)

// 还有两个特殊的值:null和undefined
null // 用来表示刻意设置成的空值
undefined // 用来表示还没有设置的值

// null, undefined, NaN, 0 和 "" 都是假的(false),其他的都视作逻辑真
// 注意 0 是逻辑假而  "0"是逻辑真, 尽管 0 == "0".

///////////////////////////////////
// 2. 变量、数组和对象

// 变量需要用 var 这个关键字声明. Javascript是动态类型语言
// 所以你在声明时无需指定类型。 赋值需要用 = 
var someVar = 5

// 如果你在声明时没有加var关键字,你也不会得到错误
someOtherVar = 10

// ...但是此时这个变量就会拥有全局的作用域,而非当前作用域

// 没有被赋值的变量都会返回undefined这个值
var someThirdVar // = undefined

// 对变量进行数学运算有一些简写法
someVar += 5 // 等价于 someVar = someVar + 5; someVar 现在是 10 
someVar *= 10 // 现在 someVar 是 100

// 自增和自减也有简写
someVar++ // someVar 是 101
someVar-- // 回到 100

// 数组是任意类型组成的有序列表
var myArray = ["Hello", 45, true]

// 数组的元素可以用方括号下标来访问
// 数组的索引从0开始
myArray[1] // = 45

// javascript中的对象相当于其他语言中的字典或映射:是键-值的集合
{key1: "Hello", key2: "World"}

// 键是字符串,但是引号也并非是必须的,如果键本身是合法的js标识符
// 而值则可以是任意类型的值
var myObj = {myKey: "myValue", "my other key": 4}

// 对象的访问可以通过下标
myObj["my other key"] // = 4

// ... 或者也可以用 . ,如果属性是合法的标识符
myObj.myKey // = "myValue"

// 对象是可变的,键和值也可以被更改或增加
myObj.myThirdKey = true

// 如果你想要访问一个还没有被定义的属性,那么会返回undefined
myObj.myFourthKey // = undefined

///////////////////////////////////
// 3. 逻辑与控制结构

// if语句和其他语言中一样
var count = 1
if (count == 3){
    // count 是 3 时执行
} else if (count == 4) {
    // count 是 4 时执行
} else {
    // 其他情况下执行 
}

// while循环
while (true) {
    // 无限循环
}

// Do-while 和 While 循环很像 ,但前者会至少执行一次
var input
do {
    input = getInput()
} while (!isValid(input))

// for循环和C、Java中的一样
// 初始化; 继续执行的条件; 遍历后执行.
for (var i = 0; i < 5; i++){
    // 遍历5次
}

// && 是逻辑与, || 是逻辑或
if (house.size == "big" && house.colour == "blue"){
    house.contains = "bear"
}
if (colour == "red" || colour == "blue"){
    // colour是red或者blue时执行
}

// && 和 || 是“短路”语句,在初始化值时会变得有用 
var name = otherName || "default"

///////////////////////////////////
// 4. 函数、作用域、闭包

// JavaScript 函数由function关键字定义
function myFunction(thing){
    return thing.toUpperCase()
}
myFunction("foo") // = "FOO"

// 函数也可以是匿名的:
function(thing){
    return thing.toLowerCase()
}
// (我们无法调用此函数,因为我们不知道这个函数的名字)

// javascript中的函数也是对象,所以函数也能够赋给一个变量,并且被传递
// 比如一个事件处理函数:
function myFunction(){
    // this code will be called in 5 seconds' time
}
setTimeout(myFunction, 5000)

// 你甚至可以直接把一个函数写到另一个函数的参数中

setTimeout(function myFunction(){
    // 5秒之后会执行这里的代码
}, 5000)

// JavaScript 仅有函数作用于,而其他的语句则没有作用域
if (true){
    var i = 5
}
i // = 5 - 并非我们在其他语言中所得到的undefined

// 这就导致了人们经常用一种叫做“即使执行匿名函数”的模式
// 这样可以避免一些临时变量扩散到外边去
function(){
    var temporary = 5
    // 我们可以访问一个全局对象来访问全局作用域
    // 在浏览器中是 'window' 这个对象。 
    // 在Node.js中这个对象的名字可能会不同。
    window.permanent = 10
    // 或者,我们也可以把var去掉就行了
    permanent2 = 15
}()
temporary // 抛出引用异常
permanent // = 10
permanent2 // = 15

// javascript最强大的功能之一就是闭包
// 如果一个函数在另一个函数中定义,那么这个函数就拥有外部函数的所有访问权
function sayHelloInFiveSeconds(name){
    var prompt = "Hello, " + name + "!"
    function inner(){
        alert(prompt)
    }
    setTimeout(inner, 5000)
    // setTimeout 是异步的,所以这个函数会马上终止不会等待。
    // 然而,在5秒结束后,inner函数仍然会弹出prompt信息。
}
sayHelloInFiveSeconds("Adam") // 会在5秒后弹出 "Hello, Adam!" 

///////////////////////////////////
// 5. 对象、构造函数与原型

//  对象包含方法
var myObj = {
    myFunc: function(){
        return "Hello world!"
    }
}
myObj.myFunc() // = "Hello world!"

// 当对象中的函数被调用时,这个函数就可以通过this关键字访问这个对象
myObj = {
    myString: "Hello world!",
    myFunc: function(){
        return this.myString
    }
}
myObj.myFunc() // = "Hello world!"

// 但这个函数访问的其实是其运行时环境,而非定义时环境
// 所以如果函数所在的环境不在当前对象的环境中运行时,就运行不成功了
var myFunc = myObj.myFunc
myFunc() // = undefined

// 相应的,一个函数也可以被指定为一个对象的方法,并且用过this可以访问
// 这个对象的成员,即使在定义时并没有绑定任何值
var myOtherFunc = function(){
    return this.myString.toUpperCase()
}
myObj.myOtherFunc = myOtherFunc
myObj.myOtherFunc() // = "HELLO WORLD!"

// 当你通过new关键字调用一个函数时,就会生成一个对象
// 而对象的成员需要通过this来定义。
// 这样的函数就叫做构造函数

var MyConstructor = function(){
    this.myNumber = 5
}
myNewObj = new MyConstructor() // = {myNumber: 5}
myNewObj.myNumber // = 5

// 每一个js对象都有一个原型,当你要访问一个没有定义过的成员时,
// 解释器就回去找这个对象的原型

// 有一些JS实现会让你通过一个对象的__proto__方法访问这个原型。
// 这虽然对理解这个对象很有用,但是这并不是标准的一部分
// 我们之后会通过标准方式来访问原型。
var myObj = {
    myString: "Hello world!",
}
var myPrototype = {
    meaningOfLife: 42,
    myFunc: function(){
        return this.myString.toLowerCase()
    }
}
myObj.__proto__ = myPrototype
myObj.meaningOfLife // = 42

// This works for functions, too.
myObj.myFunc() // = "hello world!"

// 当然,如果你要访问的成员在原型当中也没有定义的话,解释器就会去找原型的原型。
myPrototype.__proto__ = {
    myBoolean: true
}
myObj.myBoolean // = true

// 这其中并没有对象的拷贝。每个对象的原型实际上是持有原型对象的引用
// 这说明当我们改变对象的原型时,会影响到其他以这个原型为原型的对象
myPrototype.meaningOfLife = 43
myObj.meaningOfLife // = 43

// 我们知道 __proto__ 并非标准规定,实际上也没有办法更改已经指定好的原型。
// 但是,我们有两种方式可以为新的对象指定原型。

// 第一种方式是 Object.create,这个方法是在最近才被添加到Js中的
// 也因此并不是所有的JS实现都有这个放啊
var myObj = Object.create(myPrototype)
myObj.meaningOfLife // = 43

// 第二种方式可以在任意版本中使用,不过需要通过构造函数。
// 构造函数有一个属性prototype。但是这 *不是* 构造函数本身的函数
// 而是通过构造函数和new关键字生成新对象时自动生成的。
myConstructor.prototype = {
    getMyNumber: function(){
        return this.myNumber
    }
}
var myNewObj2 = new myConstructor()
myNewObj2.getMyNumber() // = 5

// 字符串和数字等内置类型也有通过构造函数来创建的包装类型
var myNumber = 12
var myNumberObj = new Number(12)
myNumber == myNumberObj // = true

// 但是它们并非严格等价
typeof myNumber // = 'number'
typeof myNumberObj // = 'object'
myNumber === myNumberObj // = false
if (0){
    // 这段代码不会执行,因为0代表假
}
if (Number(0)){
    // 这段代码会执行,因为Number(0)代表真
}

// 但是,包装类型和内置类型共享一个原型
// 这样你就可以给内置类型也增加一些功能
String.prototype.firstCharacter = function(){
    return this.charAt(0)
}
"abc".firstCharacter() // = "a"

// 这个技巧可以用来用老版本的javascript子集来是实现新版本js的功能
// 这样就可以在老的浏览器中使用新功能了。

// 比如,我们知道Object.create并没有在所有的版本中都实现
// 但是我们仍然可以通过这个技巧来使用
if (Object.create === undefined){ // 如果存在则不覆盖
    Object.create = function(proto){
        // 用正确的原型来创建一个临时构造函数
        var Constructor = function(){}
        Constructor.prototype = proto
        // 之后用它来创建一个新的对象
        return new Constructor()
    }
}