6.1 对象
6.11 对象创建
1 | //对象 |
6.12 对象属性
在 js 中,对象要怎么增加属性呢,有以下这几种方法:
1 | var obj={}; |
object 增加属性不需要声明,直接使用就可以,例如:
1 | obj.f1=function(){ |
创建了对象属性之后,想要删除修改要怎么做呢?删除属性,首先是先查询一个属性是否存在。例如:
1 | //1.in |
当结果为 true 时,表示系统查询到了该属性,接下去就是删除,删除一般就一个方法,delete :
1 | //delete |
这是属性的删除方法。
那改的话怎么改呢,修改属性其实很简单的,直接上手改动就行:
1 | obj.a=123; |
在这里,a 已经被改掉了值。然后呢,有一个东西叫做枚举,看到这,是不是大家就会感觉到其实 object 跟数组极其相似。枚举是什么呢?
1 | for(var p in obj){ |
我们调用 toString 的时候,结果也是跟 for 循环的结果一样,所以说,toString 并不是直接挂在 obj 上面的,它是挂在它底下的原形上的,这是一种类似继承的方法。 javascript 所面对的环境并不是很复杂的环境,主要是脚本前端的渲染,交互和一些游戏这些方面的语言,给人一眼看过去就会的语言。能够枚举的东西都是它自己的,不能枚举的都是它从别人那继承过来的,而且这种继承关系更像是一种委托。就类似于设计界找枪手的例子,为了自己的某种目的,把别人的东西变成自己的。怎么检测一个东西是否可以枚举呢?
1 | console.log(obj.propertyIsEnumerable('a'));//true |
6.13 自定义构造函数
我们现在的对象都是从 object 里面生成出来的,在上面加上我们想要添加的东西,那我们是不是每做一个对象,都要重复这样操作呢,其实不是的,有一个东西叫做构造函数。返回对象创建那块,如果我们需要两个对象的情况下,怎么做,直接 copy ?当然是 no 了,这样修改其中一个对象的话也会麻烦,更加不符合我们之前说的同样的事情要用同样的过程把它分装起来,类似的步骤,我们可以把它分装在固定的位置,好处就是以后在不同的地方调用,改一个函数即可。
所以,我们需要一种函数能够每次以相同的方式构造对象,同时,在改动这个函数所有的对象都能跟着改变,这就需要一个构造函数,说的通俗点就是 class 。构造函数名称以大写字母开头,这只是约定俗成,并不是语言要求。举例说下,
1 | function Student(name,age,gender){ |
在这边呢,有三种命名规则:
匈牙利命名规则:属性 + 类型 + 对象描述
属性:成员变量 m_ 静态成员 s_ 普通 <没有>
类型:整形 i ,数组 a ,字符串 str
对象描述:单词 + 单词。。。。,首字母大写
小驼峰命名规则:对象描述
第一个单词的首字母小写,其他单词的首字母大写
大驼峰命名规则:对象描述
第一个单词的首字母大写,其他单词的首字母大写
命名是很重要的,体现了我们代码人的素养。越后来,可能越来越少的人会使用匈牙利命名规则,特别是 window 不是特别热的情况下,所以,小驼峰和大驼峰会成为大多数人的趋向。我们会发现构造函数使用的是大驼峰命名规则,而 object 对象的成员函数使用的是小驼峰命名规则。
6.14 包装类
Number() vs number
1
2
3
4
5
6
7
8
9var n1=123;
var n2=new Number(123);//构造函数
console.log(typeof(n1));//number
console.log(typeof(n2));//object
var n3=n1+n2;
console.log(n3);//246String() vs string
1
2
3
4
5var s1='aaa';
var s2=new String('aaa');
console.log(typeof(s1));//string
console.log(typeof(s2));//objectBoolean() vs boolean
1
2
3
4
5var b1=true;
var b2=new Boolean(true);
console.log(typeof(b1));//boolean
console.log(typeof(b2));//object包装类的隐式调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17var arr=[1,2,3,4];
arr.length=2;
console.log(arr);//[1,2]
var str='1234';
//str.length=2;
var strTmp=new String(str);
strTmp.length=2;
//strTmp 摧毁
console.log(str);//1234
var iNum=123;
//iNum.toString();
var iTmp=new Number(iNum);
iTmp.toString();
//iTmp 摧毁
console.log(iNum);//123
6.2 预编译
JavaScript 引擎的三大步骤:
预编译(第一次\前置扫描)
脚本:
创建全局对象 GO(window) (上下文)
加载脚本文件
预编译
- 找出所有的变量声明,按照变量名加入全局对象,如果已经存在,忽略。
- 找出所有的函数声明,按照函数名加入全局对象,如果已经存在同名变量或者函数,替换。
- 非声明不予理睬
解释执行
解释执行(第二次扫描)
6.21 预编译——脚本
编译呢,是指把代码变成块结构的语言,就是机器码,然后把编好的块链接起来,组装成 .exe 或者是 .dl 的过程叫做链接。预编译其实是编译里最早的东西。在 c++ 里面,我们拿到一个函数都是先把它声明,再去实现,那么,预编译是把所有的声明都扫描出来,扫描到一个名字表里面去,然后知道这个东西以后,在编译时才能知道是否有这么一个东西。
没有 var 的变量(都不是变量声明),全部认为是 window 的全局变量,不参与预编译。举个简单的例子:
1 | aa=5; |
这个结果是毫无疑问的,那么,我们再来看个,
1 | console.log(aa); |
结果是会报错,那如果按照预编译的理解,改成这样子呢,
1 | console.log(aa);//undefied |
为什么会这样子?很简单,就是我们上面说的,这是段脚本语言,拿到这个脚本,先是创建全局对象,然后按照流程,找出所有的变量声明,这边只有 aa ,把变量名 aa 加入全局对象,即aa 已经存入 window 中,没有函数声明,就直接继续执行,执行的话,第一行语句,因为 aa 已经存进 window 中,但是没有定义,所以输出的结果是 undefined ,然后第二行语句,将5赋值给 aa ,所以第三行语句输出5。
即使在函数中,aa 也是全局变量,是在运行时,而不是定义时。举个例子:
1 | function test(){ |
大家根据预编译的步骤想想,这样会不会报错,会的,因为 test() 函数并没有被纳入 window 中,想让程序不报错的话,我们要先定义函数,让它首先被 window 存入,例如:
1 | test(); |
这样子结果就是正确的。
在脚本中,所有变量声明在脚本的预编译阶段完成,所有变量的声明与实际的书写位置无关。例如:
1 | console.log(aa); |
脚本中,所有函数声明在脚本的与编译阶段完成,所有函数的声明与实际书写位置无关。例如,
1 | console.log(haha); |
脚本中,如果变量与函数同名,那么,函数将覆盖变量。举例:
1 | console.log(haha); |
在这里,函数就将变量给覆盖掉,不然的话,运行结果应该在本结果的前面加一个 undefined 。
脚本中,只有函数能够覆盖变量,变量无法覆盖函数。例如:
1 | console.log(haha); |
脚本中,后面的函数声明会覆盖前面的函数声明,并且会忽略参数。例如:
1 | haha(1); |
6.22 预编译——函数调用
函数调用:
创建活动对象 AO(Active Object)
预编译:
- scope chain
- 初始化 arguments
- 初始化形参,将 arguments 中的值赋给形参
- 找出所有的变量声明,按照变量名加入 AO ,如果已经存在,忽略。
- 找出所有的函数声明,按照函数名加入 AO ,如果已经存在同名变量或者函数,替换。
- this 初始化
解释执行
函数中,所有变量声明,在函数的预编译阶段完成,所有变量的声明与实际书写位置无关。例如:
1 | function f(){ |
函数中,所有函数声明在函数的预编译段完成,所有函数的声明与实际书写位置无关。例如:
1 | function f(){ |
函数中,如果变量与函数同名,那么,函数将覆盖变量。举例,
1 | function f(){ |
函数中,只有函数能够覆盖变量,变量无法覆盖函数。例如:
1 | function f(){ |
函数中,后面的函数声明会覆盖前面的函数声明,并且会忽略参数。例如:
1 | function f(){ |
当函数预编译后,遇到需要访问的变量或者函数,优先考虑自己 AO 中定义的变量和函数,如果找不到才会在其定义的上一层 AO 中寻找
,如果再找不到才会在其定义的上一层 AO 中寻找,直到到达 GO 。例如:
1 | var scope='global'; |
首先是脚本的预编译,变量 scope , t 已经放入 window 中, 大致思路如下:
1 | GO: |
看个前端工程师的面试题:
1 | console.log(scope);//undefined |
遵循函数调用的原则,大致思路如下:
1 | //GO: |