[toc]
第一门课主要学习JS语法,但是最好还是之后看一下JS红宝书
1.简史
这是我去先看了看红宝书第一章
JS被用于处理表单内容,但让网景公司成为了老大,微软眼红并仿写出了JScript,这是一个基于JS的实现,但由于两者不统一,最后1997年Ecma(欧洲计算机制造商协会)的TC39委员会制定出标准,花费数月时间出台了传说中的ECMA-262,也就是ECMAScript伪语言,此后,各浏览器以此作为实现的依据,但是他们的实现仍有偏好性
虽然JS和ECMAScript基本是同义词,但是完整的JS实现应包含:
- 核心 ECMAScript
- 文档对象模型 DOM
- 浏览器对象模型 BOM
ECMAScript连输入输出都没有,他只是一个基准
Web浏览器是其实现的一种宿主环境(node.js也是一种),提供ECMAScript的基准实现和与环境自身交互必须的拓展!
拓展,比如DOM,使用ECMAScript核心类型和语法,提供特定于环境的额外功能,
ESMAScript描述了这门语言的:语法,类型,语句,关键字,保留字,操作符,全局对象
现在都到ES12了。。。。
ECMAScript的符合性具备极大的自由度,给了实现开发者很大的权限!
好多东西没写,一定要到时候好好看看红宝书,太棒了!
2.Html中的JS
看不懂。。等以后
3.语言基础
我还是结合着看吧,红宝书全,但是视频有实战经验。
嵌入HTML
主要方法是<script>方法,后来被正式加入到HTML规范,他有8个属性,没几个看得懂
但是defer这个属性使得script不用放在body前(兼容性不好,而且有时候可能出现顺序错乱,最好是只有一个这样的脚本,更好还是放在body的最后。。。),src允许使用外部js文件,integrity防止你在引用其他网站js文件时接受到恶意内容,async有很多限制,不推荐使用。。。
从上到下解释,浏览器解析行内脚本的方式决定了即使字符串出现</script>也会结束!
浏览器按照script出现的顺序解释他们,除了defer和async属性。
引用外部时,script标签之间绝对不能写代码
还有一些比如“文档模式”啥的鬼东西,看不懂。。。
<noscript>元素用于优雅降级,先用于那些金庸JS的浏览器,在浏览器不支持脚本或者支持被关闭时,将会渲染在noscript标签中的内容,否则则会被忽视掉,
前提
单行注释// 对应快捷键 ctrl + /
多行注释和c语言一样,是/* / ,对应快捷键*ctrl+shift+/ 改了以后
左下角设置可以直接更改快捷方式
Js标识符可使用 字母 数字 下划线 美元符号 但是开头不能是数字,惯例是驼峰大小写!
严格模式
所有浏览器都支持严格模式,会在遇到不规范写法时抛出错误
在整个脚本或者某个具体函数开头加上 “use strict”;
来开启
分号不是必须,但是有助于防止很多问题,也有利于压缩代码,提升性能等
和C一样,也用{}标识一个代码块
if类语句和C一样在多条时,必须要代码块,但好习惯是一句也要加代码块
很多关键字都在各个语言见过,没见过的有
catch delete extends finally instanceof super throw
还有一些未来的保留字
enum implements interface let package protected await
变量
ECMAScript变量松散,可以存储任何类型数据,ES6之后才可以用let const ,var啥时都能用
基本使用无二,不知道为啥说不推荐改变变量存储的类型,但是完全有效
在函数内部var会创建局部变量,但是省略var可以创建一个全局变量!
但是这种做法不被推荐,甚至在严格模式下会抛出ReferenceError
*var声明提升!
function foo(){
语句1
var age = 26;
}
实际上被ECMAScript理解为
var age;
语句1;
age = 26
这使得变量可以后声明,也使得变量可以重复声明
var的作用域是函数作用域,if块中声明的var可在外面调用
let声明的范围是块作用域,而且不允许冗余声明(SyntaxError)
但是由于JS引擎会记录变量声明的标识符和所在的块作用域,可以嵌套使用相同的标签
冗余报错会发生在同时使用var和let时,两个关键字声明的变量是相同的,只是指出变量在相关作用域如何存在。
暂时性死区
只要块级作用域内存在let
命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响。
var tmp = 123;
if (true) {
tmp = 'abc'; // ReferenceError
let tmp;
}
上面代码中,存在全局变量tmp
,但是块级作用域内let
又声明了一个局部变量tmp
,导致后者绑定这个块级作用域,所以在let
声明变量前,对tmp
赋值会报错。
ES6 明确规定,如果区块中存在let
和const
命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。
总之,在代码块内,使用let
命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)。
这甚至会导致typeof出错,
与var关键字不同,let在全局作用域中声明的变量不会成为window对象的属性?不懂。。
const声明时必须同时初始化,而且不能修改,限制只适用于它指向的变量的引用,跟python一样
对for-of for-in循环中的不会修改的变量特别有意义
风格和最佳实践!
- 不使用var
- const优先,let次之
数据类型
对象的转换:如果操作数是对象,则这个对象将先使用valueOf()转换成原始值,如果结果还不是原始值,则再使用toString()方法转换;
6种原始(简单)数据类型:Undefined Null Boolean Number String Symbol(ES6)
1种复杂数据类型:Object,这是一种无序名值对的集合
ES中不能定义自己的数据类型,但这些类型很灵活,一种能当多种数据类型用
typeof 操作符,它不是函数,返回数据类型,也可以判断函数(返回’function’)
虽然函数也被ES认为是对象,但它们有特殊的属性,因此被typeof独立区分
Undefined
只有一个值undefined, var/let声明对象但未初始化就会赋予undefined
他理论上永远不用与显式赋值,只是为了区别空对象指针null和未初始化指针
但由于未声明的变量typeof也会返回undefined,所以最好还是在声明时就初始化
这是一个假值,但是也有很多其他的假值,所以不要依靠真假判断是否是undefined
Null
只有一个值null,逻辑上讲,null值表示一个空对象指针,所以typeof null返回object
null 有属于自己的类型 Null,而不属于Object类型,typeof 之所以会判定为 Object 类型,是因为JavaScript 数据类型在底层都是以二进制的形式表示的,二进制的前三位为 0 会被 typeof 判断为对象类型,而 null 的二进制位恰好都是 0 ,因此,null 被误判断为 Object 类型。
声明时如果暂时不用赋值,最好赋一个null
undefined是由null值派生的,所以表面相等(null == undifined)
这是一个假值,但是也有很多其他的假值,所以不要依靠真假判断是否是null
Boolean
俩值,true false,但是此处的布尔值不同于数值,所以不再是等于 1 和 0(哈哈,特意提醒)
所有类型的值都有相应布尔值的等价形式,通过Boolean()转型函数转换
数据类型 | true值 | false值 |
---|---|---|
Boolean | true | false |
String | 非空字符串 | “” |
Number | 非零值(包括无穷) | 0 NaN |
Object | 任意对象 | null |
Undefined | undefined |
非常重要,因为if等流控制语句会自动执行该转换
Number
整数
八进制和十六进制规则和C一样,但是八进制如果写错了直接忽略0当成十进制,数学操作中都当成十进制
严格模式不能使用八进制前缀0,要用0o
由于JS保存数值的方式,会存在正零,负零,是等价的
浮点数
存储空间是整数两倍,所以JS会尽可能把它们变成整数,如果小数点后为0,直接给你改成整数(如21.0)
ES会将小数点后至少6个0的浮点数转换为科学计数法
浮点数精确度高达17位小数,但是也存在如C般很小的偏差问题,因此不要用 == 对待浮点数
这是IEEE 754数值标准导致的。。
最小值Number.MIN_VALUE 5e-324 再小 -Infinity(Number.NEGATIVE_INFINITY)
最大值Number.Max_VALUE 1.79………………e+308, 再大 Infinity(Number.POSITIVE_INFINITY)
0作分子返回NaN(not a number) 作分母返回正负Infinity
NaN有一些诡异的属性:
- 任何涉及NaN的操作始终返回NaN(如NaN/10)
- NaN不等于包括自己在内的任何值!
数值转换:
Number() parseInt() parseFloat()
Number的转换规则和一元加操作符一样,值得注意的是:
null返回0,undefined返回null。空字符串返回0
字符串包含有效的16进制,会转化成对应的十进制值。
如果字符串包含正常情况外的词,返回NaN。
对于对象:先调用valueof()方法,再按照正常规则转换,如果结果是NaN,则先调用toString,再按字符串规则转换。
所以必须用于十分标准的字符串才能得到合适输出,所以一般优先采用下面俩方法
parseInt规则:
跳过空白,开始检索,第一个不是数值,加减号,返回NaN,否则开始检索至结束或非法。
未加第二个参数则检索0x开头,0开头,最好加上,可以省略前缀
parseInt(“10100101”, 2),表示按二进制解析!
parseFloat类似,但忽略开头的0,只用于十进制,若结果为整数,会返回整数
String
可用单,双,没有语法意义。。。pink推荐使用单引号
转义字符(字符字面量)(算一个字符)基本无二,\xnn ASCII \unnnn nnnn表示Unicode字符
字符串是不可变的!任何更改都导致原字符串销毁
toString方法可用于数值,布尔值,对象,字符串值(返回副本),
null/undefined没有,调用则返回本身的字符串null->”null”
在对待数值时可以指定显示的进制
let num = 10;
console.log(num.toString(2)) -"1010"
模板字面量
通过反引号定义(但是这里书写用单引号。。不然变成代码块我靠),保留内部的空白结构
技术上讲,模板字面量不是字符串而是一种特殊的JS句法表达式,只不过求之后得到的是字符串
字符串插值
模板字面量求值时立刻转换成字符串实例,任何插入的变量也会从它们最接近的作用域中提取
‘${}’来进行调用,可以变量,会将表达式用toString转化成字符串,可以调用函数和方法,可以插入自己以前的值
value = ‘${value}abc’ 附加abc
标签函数
用来自定义插值行为,直接把定义的函数放在模板字面量前即可
function xxx(){}
let a = xxx’….’
注意传进去的第一个参量是[“”,”+”,]这种,其中所有的$被换成空字符串,符号保留,经常采用“剩余操作符”收集不确定的参数
原始字符串则调用了默认的String.raw标签函数,也可通过string(某字符串变量).raw来取得原始内容
Symbol
ES6新增,符号是原始值,符号实例唯一,不可变
用途是确保对象属性使用唯一标识符,不会发生属性冲突的危险
符号需要使用Symbol()函数初始化,
Object
JS对象实质是一组数据和功能的集合,通过new+对象类型的名称来创建
可以通过创建Object实例来创建对象,再添加属性和方法
每个对象内置了一大堆方法,p56
valueof方法将对象转换为原始值。你很少需要自己调用valueOf
方法;当遇到要预期的原始值的对象时,JavaScript会自动调用它。如果对象没有原始值,则valueOf
将返回对象本身。JavaScript的许多内置对象都重写了该函数,以实现更适合自身的功能需要。因此,不同类型对象的valueOf()方法的返回值和返回值类型均可能不同。此外你也可以自己写valueof来覆盖
对象 | 返回值 |
---|---|
Array | 返回数组对象本身。 |
Boolean | 布尔值。 |
Date | 存储的时间是从 1970 年 1 月 1 日午夜开始计的毫秒数 UTC。 |
Function | 函数本身。 |
Number | 数字值。 |
Object | 对象本身。这是默认情况。 |
String | 字符串值。 |
Math 和 Error 对象没有 valueOf 方法。 |
你可以在自己的代码中使用valueOf
将内置对象转换为原始值。 创建自定义对象时,可以覆盖Object.prototype.valueOf()
来调用自定义方法,而不是默认Object
方法。
MyNumberType.prototype.valueOf = function() { return customPrimitiveValue; };
操作符
ES中的操作符可用于各种类型的值,在处理对象时会调用valueOf和toString来处理(C++重构吗)
因为数据类型有限,不会出现重构等问题,因此符号的所有规则都可以列出来!
一元运算符
++ – 和C差不多,可用于任意值,应用后均变为数值类型
字符串非法则NaN,false和true变成0和1,对象先调用valueof()方法,再按照正常规则转换,如果结果是NaN,则先调用toString,再按字符串规则转换。
+ and - 会对非数值进行Number()转换,
位操作符
虽然实际64位,但位操作时仅应用32位,第32位0正1负,无符号数就大一倍。
正值前31位正常,负值则是补码。(这里称二补数)
但特殊值NaN和Infinity在位操作中被当成0
C有的这里都有!右移使用符号位来填补空位,无符号右移>>>
则用0来填补!
布尔操作符
非操作符 ! 无论对什么数据类型,先转换成布尔值,然后取反
!! 相当于调用Boolean()
&&逻辑与可以用于任何类型的操作数,不限于布尔,同样具有短路特性,如果操作数不全为布尔,则逻辑与并不一定返回布尔值:
- 如果第一个操作数是对象,返回第二个操作数
- 如果两个操作数都是对象,返回第二个操作数
- 如果第二个操作数为对象,只有第一个操作数为true时才会返回该对象
- 只要出现null/NaN,undefined,就返回相应的
||逻辑或,也会短路,如果操作数不全为布尔,则同样:
- 如果第一个操作数是对象,返回第一个操作数
- 如果第一个操作数求值为false,则返回第二个操作数
- 如果第两个操作数是对象,返回第一个操作数
- 如果俩都为null/NaN,undefined,就返回相应的
乘性运算符
* 也会在处理非数值自动调用Number()
特性:
- 任意操作数NaN返回NaN
- Infinity * 0 ->NaN
- Infinity相乘,或乘有限数,返回+-Infinity
/ 特性:
- 有NaN,返回NaN
- 无限除无限返回NaN
- 无限除有限(包括0)返回对应无穷
- 0 / 0 返回NaN
- 有限值 / 0 返回正负无穷
% :
- 无限除任何 NaN
- 有限除 0 NaN
- 有限除无限 有限
- 0除 非0 0
指数操作符
** 和 **==
加性操作符
+ 注意:
- 有NaN 返回NaN
- 无穷+负无穷 返回NaN
- 0相加,都是-0返回-0,否则返回+0
- 俩字符串则拼接
- 不然就转换成字符串,如果有undefined和null,变为”undefined”然后再拼接
- 注意:
- 同类无穷相减(其实就是难以判断正负的时候)为NaN
- 无穷相减,可判断正负则返回对应无穷
- 0相减,同号得正0,异号得-0
- 会转化成数值而不是字符串,对象也是和以前一样的处理
关系运算符
还是四个,只返回布尔值
- 出现数值,全转为数值比较
- 都是字符串则逐个比较(大写字母比小写字母顺序小)
- 对象先调用valueof()方法,再按照正常规则转换,如果没有valueof操作符,则调用toString,再按字符串规则转换。
- 布尔被转化为0和1
- 只要出现NaN,结果就为false
- null和undefined好像也是
1、大于运算符
大于运算符的操作数可能是任意类型,然而,只有数字和字符串才能真正执行比较操作,因此那些不是数字和字符串的操作数都将进行类型转换。规则如下:
如果操作数是对象,则这个对象将先使用valueOf()转换成原始值,如果结果还不是原始值,则再使用toString()方法转换;
在对象转换为原始值之后,如果两个操作数都是字符串,则按照字母表的顺序对两个字符串进行比较,这里提到的字母表顺序是指组成这个字符串的16位unicode字符的索引顺序;
在对象转换为原始值之后,如果至少有一个操作数不是字符串,则两个操作数都转换成数字进行比较。
需要注意的是Javascript字符串是一个由16位整数值组成的序列,字符串的比较也只是两个字符串中的字符的数值比较,由unicode定义的字符编码和任何特定语言或者本地语言字符集中的传统字符编码顺序不尽相同。字符串比较是区分大小写的,所以一般首先会将字符串通过String.toLowerCase()或者是String.toUpperCase()做大小写的转换。
2、大于等于运算符
大于等于运算符并不依赖于大于或等于运算符的比较规则,而是遵循小于运算符的比较规则,结果取反
3、小于等于运算符
小于等于运算符(<=)并不依赖于小于或等于运算符的比较规则,而是遵循大于运算符的比较规则,结果取反。
4、小于运算符
小于运算符(<)用于比较两个操作数,如果第一个操作数小于第二个操作数,则小于运算符的计算结果为true,否则为false。
所以要考虑到底是不满足条件,还是出现了NaN
大写字母的排序在小写字母前面,所以偏小
按照规则,null和undifined
相等操作符
两组,等于和不等于 == , 全等和不全等 ===
第一组进行强转再判断:
- 布尔->数值
- 字符串比数值,字符串->数值
- 对象,用valueof对象再比较
- null == undefined
- null undefined不能转化为其他类型的值再比较,因此不等于false,因为false->0
- 有NaN就返回false,不相等返回true
全等不转换!null !== undefined 因为数据类型不同
条件运算符 () ? :
赋值运算符 >>>=
逗号运算符
语句
也称流控制语句
if中自动调用Boolean,也有do-while,while,swtich(break规则一样的)
for - in是一种严格的迭代语句,用于枚举对象中的非符号键属性(如window对象的属性)
for(const property in expression) {statement;}
这里的const不是必须的,但是为了确保局部变量不被修改,推荐使用const
ES中的对象属性是无序的,for-in不能保证返回的顺序,甚至因浏览器而异
如果迭代的变量是null / undefiend 则不执行循环体
for-of 是一种严格的迭代语句,用于遍历可迭代对象的元素(如数组项)
for(const property of expression) {statement;}若尝试迭代的变量不支持迭代则抛出错误
标签语句
在语句前可以通过标签语句加标签,常用于嵌套循环中
break和continue + 标签可以生效后跳到label位置
with语句将代码作用域设置为特定的变量
严格模式下不允许使用with
with(location){………}
在with语句内部,每个变量首先被认为是局部变量,如果不是,就搜索location对象,看他是否有一个同名属性,使得话,该变量就被求值为location对象的同名属性
swtich特性:
可以用于所有数据类型,case后面不加括号,可以加表达式!
甚至可以swtich(true)然后case里面放一堆判别式,不过case判断是全等!
与多if相比,switch效率高,适合情况较多且比较确定,多if适合范围判断,情况较少
函数
4.变量,作用域/内存
变量可存储两种类型的数据:原始值,引用值(由多个值构成的对象)
分别采用按值访问和按引用访问,很多语言中字符串是使用对象访问的,因此被认为是引用类型,但是ES不是
引用值可以随意添加,修改和删除属性和方法
原始值不能有属性,但是添加时不会报错,但试图引用会返回undefined.
复制的时候,原始值是复制,引用值是引用原来内存里的变量,而不是创建副本
但是所有函数的参数都是按值传递的,只不过引用值传进去的时候传的是一个指向(类似指针),所以在里面修改外面也能体现,但仍然是按值传递。
typeof能区分是否是对象,但是它却不能区分对象的具体类型,对引用值意义不大
instanceof对给定引用类型的对象十分有用,比如 person instanceof Object 变量person是Object吗?
所以该操作符检测任何引用值和Object构造函数都会返回true,但是检测原始值则始终返回false
上下文
变量或者函数的上下文决定了可以访问的数据和行为.全局,函数,块级
每个上下文都有一个关联的变量对象,虽然代码无法访问,但是后台处理数据可能用到
全局上下文是最外层的,根据实现的宿主,可能对象不一样,在浏览器中全局上下文是window对象
所有通过var方法定义的全局变量和函数都会成为对象的属性和方法
上下文在其所有代码执行完成后会销毁,包括所有定义在它上面的变量和函数,全局上下文则在应用程序退出前才会销毁,比如关闭网页或者退出浏览器,函数参数被视为当前上下文的变量
上下文的代码在执行的时候,会创建变量对象的一个作用域链,从当前上下文往外直到全局上下文
自己理解下吧。。。
个人理解,声明被提升到最前面,而赋值仍留在原地
作用域增强?
通常有两种情况:
- with(location)的location对象会被添加到作用域链的前端。
- try/catch 回创建一个新的变量对象,该对象包含即将抛出的错误的声明。
执行上下文的生命周期
执行上下文的生命周期包括三个阶段:创建阶段 → 执行阶段 → 回收阶段,本文重点介绍创建阶段。
- 创建阶段
当函数被调用,但未执行任何其内部代码之前,会做以下三件事:
- 创建变量对象:首先初始化函数的参数 arguments,提升函数声明和变量声明。下文会详细说明。
- 创建作用域链(Scope Chain):在执行期上下文的创建阶段,作用域链是在变量对象之后创建的。作用域链本身包含变量对象。作用域链用于解析变量。当被要求解析变量时,JavaScript 始终从代码嵌套的最内层开始,如果最内层没有找到变量,就会跳转到上一层父作用域中查找,直到找到该变量。
- 确定 this 指向:包括多种情况,下文会详细说明
在一段 JS 脚本执行之前,要先解析代码(所以说 JS 是解释执行的脚本语言),解析的时候会先创建一个全局执行上下文环境,先把代码中即将执行的变量、函数声明都拿出来。变量先暂时赋值为 undefined,函数则先声明好可使用。这一步做完了,然后再开始正式执行程序。
另外,一个函数在执行之前,也会创建一个函数执行上下文环境,跟全局上下文差不多,不过 函数执行上下文中会多出 this arguments 和函数的参数。
- 执行阶段
执行变量赋值、代码执行
- 回收阶段
执行上下文出栈等待虚拟机回收执行上下文
变量声明
和之前一样,赋值为const的对象变量不能被重新赋值为引用值,但对象的键却不受限制
要想对象不能修改,可以 const variable = Object.freeze({})
这样会“静默失败”,即不提示出错,但是调用则返回undefined
应该尽可能使用const,除非确实需要一个将来会重新赋值的变量,以从根本上保证提前发现重新赋值导致的bug,由const声明的实例可被JS运行编译器替换成实际的值,而不会通过查询表进行变量查找,谷歌V8就是这种优化
标识符查找
使用块级作用域不会改变搜索流程,但会添加额外的层次
垃圾回收
垃圾回收有可能明显拖慢渲染的速度和帧速度
JS使用垃圾回收,即执行环境负责在代码执行时管理内存
周期性执行垃圾回收程序以处理不再使用的变量并释放其内存(近似且不完美)
历史上两种主要的标记策略:
标记清理(常用)
运行时首先标记内存中存储的所有变量,然后将所有上下文中的变量以及被上下文的变量引用的变量的标记清理掉,在此之后再被加上标记或存在标记就会被清理,标记的实现并不重要,关键是打标记的策略
引用标记
记录每个值被引用的次数,当次数为0时清除,但可能导致循环引用,神之间接导致DOM,BOM由JS实现
这对性能影响很大,如今IE7以后更新了回收的策略,极大提升了浏览器中JS性能
某些浏览器可以(不推荐)主动触发垃圾回收
内存管理
由于系统给浏览器分配内存远少于桌面应用(移动浏览器更少)所以有必要限制内存
这会影响变量分配,也影响调用栈以及能够同时在一个线程中执行的语句数量
最佳策略是只保存必要的数据再运行期间,如果数据不在必要,把它设置为null,解除引用,在垃圾回收时清理
局部变量在离开上下文时会自动解除引用,全局变量和对象要手动解除,设为null
尽量使用let和const
利用隐藏类
V8在将代码编译为实际的机器码时会利用“隐藏类”,共享隐藏类效果会更好
最好的方法是在构造函数中一次性声明所有属性,把不再需要的属性设置为null
避免先创建再单独给实例创建或删除属性
内存泄漏
可能有函数内部全局变量,定时器调用,闭包。。
静态分配和对象池(优化的极端形式,不太常见)
压榨浏览器,一个关键问题是如何减少浏览器回收垃圾的次数,可以间接触发回收条件。浏览器决定何时运行垃圾回收程序的一个标准就是对象更替的速度,如果有很多对象一下被初始化,马上又超出作用域,那么就会被浏览器用更激进的方式回收。
如果函数会初始化变量,而马上又失去引用,则会被“盯上”,一个方案是函数调用已存在的对象,给他赋值
那在哪里新建第一个已存在的对象才不会被盯上呢?一个策略是对象池:
在初始化的某一刻创建一个对象池,用来管理一组可回收的对象,其他代码可以向这里请求对象,更改或使用,然后返还它。这样垃圾回收检测就不会发现有对象更替,也就不会那么频繁地运行。
5.基本引用类型
JS没有类,虽然是面向对象,但缺少某些基本结构比如类和接口,引用类型是把数据和功能组织到一起的结构,也被称对象定义,它们描述了自己的对象应有的属性和方法。
引用值(或对象)是某个特定引用类型的实例,新对象通过new操作符后+一个构造函数来创建。
构造函数是用来创建新对象的函数,负责创建一个只有默认属性和方法的简单对象
函数也是一种引用,但是内容好多,放在第十章