JavaScript
补充:
①void 是一元运算符,它可以出现在任意类型的操作数之前执行操作数,却忽略操作数的返回值,返回一个 undefined。
②遍历器(Iterator)的作用是为所有的数据结构,提供统一、简便的访问接口;其次,es6创造了一种新的遍历命令for...of循环,Iterator接口主要供for...of消费。
1.如果一个变量未定义,直接使用,会报错;但如果在变量前面加上 typeof ,则会输出undefined,常常用于条件判断语句。
2.null == undefined //true ; null === undefined //false
3.由于JavaScript的最初设计历史,null类型可以自动转换为0(Number(null) === 0); undefined转为数组时为NaN
4.如果JavaScript预期某个位置应该是布尔值,会将该位置上有的值自动转为布尔值 。
5.NaN是JavaScript的指数值,表示 非数字,主要出现在将字符串解析成数字出错的场合。
5 - ’x‘ // NaN 在该代码运行时,会自动将字符串x
转为数值,但是由于x
不是数值,所以最后得到结果为NaN
,表示它是“非数字”(NaN
)。
另外,一些数学函数的运算结果会出现NaN
。
需要注意的是,NaN
不是独立的数据类型,而是一个特殊数值,它的数据类型依然属于Number
,使用typeof
运算符可以看得很清楚。
NaN
在布尔运算时被当作false
。
NaN
与任何数(包括它自己)的运算,得到的都是NaN
。
6.Base64转码。所谓 Base64 就是一种编码方法,可以将任意值转成 0~9、A~Z、a-z、+
和/
这64个字符组成的可打印字符。使用它的主要目的,不是为了加密,而是为了不出现特殊字符,简化程序的处理。
JavaScript原生提供了两个base64相关的方法。
btoa():任意值转为base64编码;atob():base64编码转为原来的值。
这两个方法不适合非ASDCII码的字符,会报错。要将非ASCII码字符转为base64编码,必须中间插入一个转码环节,再使用这两个方法。
function b64Encode(str) {
return btoa(encodeURIComponent(str));
} //该函数用作将ASCII字码转为base64编码
function b64Decode(str) {
return decodeURIComponent(atob(str));
} //该函数用作将base64编码转为ASCII字码
7.对象采用大括号表示,这导致了一个问题:如果行首是一个大括号,他到底是表达式还是语句?
为了避免这种歧义,JavaScript引擎的做法是,如果遇到这种情况,无法确定是对象还是代码块,一律解释为代码块。
如果要解释为对象,最好在大括号前加上圆括号。因为圆括号的里面,只能是表达式,所以确保大括号只能解释为对象。
这种差异在eval
语句(作用是对字符串求值)中反映得最明显。
eval('{foo:123}') //123
eval('({foo:123})') //{foo:123}
上面的代码中,如果没有圆括号,eval将其理解为一个代码块;加上圆括号以后,就理解成一个对象。
8.查看一个对象本身的所有属性,可以使用Object.keys方法。
9.delete命令用于删除对象的属性,删除成功后返回true。
上面代码中,delete
命令删除对象obj
的p
属性。删除后,再读取p
属性就会返回undefined
,而且Object.keys
方法的返回值也不再包括该属性。
注意,删除一个不存在的属性,delete
不报错,而且返回true
。
另外,需要注意的是,delete
命令只能删除对象本身的属性。
10.属性是否存在:in运算符。
in
运算符用于检查对象是否包含某个属性(注意,检查的是键名,不是键值),如果包含就返回true
,否则返回false
。它的左边是一个字符串,表示属性名,右边是一个对象。
in
运算符的一个问题是,它不能识别哪些属性是对象自身的,哪些属性是继承的。就像上面代码中,对象obj
本身并没有toString
属性,但是in
运算符会返回true
,因为这个属性是继承的。
这时,可以使用对象的hasOwnProperty
方法判断一下,是否为对象自身的属性。
11.属性的遍历:for ... in 循环
for...in
循环用来遍历一个对象的全部属性。
for...in
循环有两个使用注意点。
- 它遍历的是对象所有可遍历(enumerable)的属性,会跳过不可遍历的属性。
- 它不仅遍历对象自身的属性,还遍历继承的属性。
一般情况下,都是只想遍历对象自身的属性,所以使用for...in
的时候,应该结合使用hasOwnProperty
方法,在循环内部判断一下,某个属性是否为对象自身的属性。
12.toString()。函数的toString()方法返回一个字符串,内容是函数的源码。函数内部的注释也可以返回。
13.函数作用域。
14.函数内部的变量提升。与全局作用域一样,函数作用域内部也会产生“变量提升”现象。var 命令声明的变量,不管在什么位置,变量声明都会被提升到函数体的头部。
15.函数本身的作用域。
函数本身也是一个值,也有自己的作用域。他的作用域与变量一样,就是其声明所在的作用域,与其运行所在的作用域无关。
总之,函数执行时所在的作用域,是定义时的作用域,而不是调用时所在的作用域。(这一点很重要,“闭包”现象就由于此)
16.函数参数传递方式。
函数参数如果是原始类型的值(数值、字符串、布尔值),传递方式是传值传递(passes by value)。这意味着,在函数体内修改参数值,不会影响到函数外部。
但是,如果函数参数是复合类型的值(数组、对象、其他函数),传递方式是传址传递(pass by reference)。也就是说,传入函数的原始值的地址,因此在函数内部修改参数,将会影响到原始值。
注意,如果函数内部修改的,不是参数对象的某个属性,而是替换掉整个参数,这时不会影响到原始值。
17.闭包
形成原理:1.javascript语言特有的“链式作用域”结构;2.函数本身作用域。
由于JavaScript语言中,只有函数内部的子函数才能读取变量,因此可以把闭包简单理解长“定义在一个函数内部的函数”。闭包的最大特点,就是它可以“记住”诞生的环境。在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。
闭包的最大用处有两个,一个是可以读取函数内部的变量,另一个就是让这些变量始终保持在内存中,即闭包可以是的它的诞生环境一直存在。
为什么闭包能够返回外层函数的内部变量?
原因是闭包用到了外层变量,导致外层函数不能从内存中释放。只要闭包没有被垃圾回收机制清除,外层函数提供的运行环境也不会被清除,它的内部变量就是始终保存着当前值,供闭包读取。
18.eval
eval命令接受一个字符串作为参数,并将这个字符串当作语句执行。
如果eval的参数不是字符串,那么会原样返回。
eval没有自己的作用域,都是在当前作用域内执行,因此可能会修改当前作用域的变量的值,造成安全问题。
为了防止这种风险,JavaScript 规定,如果使用严格模式,eval
内部声明的变量,不会影响到外部作用域。
不过,即使在严格模式下,eval
依然可以读写当前作用域的变量。
19.运算符
1 + 1 //2
true + true //2
1 + true //2
javascript运行非数值的相加,上述两种情况,布尔值都会自动转为数值,然后再相加。
加法运算符是在运行时决定,到底是执行相加,还是执行连接。也就是说,运算子的不同,导致了不同的语法行为,这种现象称为“重载”。
比较特殊额是,如果是两个字符串相加,这时加法运算符会变成连接连接元素符,返回一个新的字符串,将两个原字符串连接在一起。
‘3’ + 4 + 5 // '345'
3 + 4 + '5' // '75'
除了加法运算符,其他算数运算符(减法、除法和乘法)都不会发生重载。他们的规则是:所有运算子一律转为数值,在进行相应的数学运算。
20.对象的相加
如果运算子是对象,必须先转成原始类型的值,然后再相加。
对象转为原始类型的值,规则如下:
(1)首先,自动调用对象的valueof方法
一般来说,对象的valueOf
方法总是返回对象自身,这时再自动调用对象的toString
方法,将其转为字符串。
(2)如果返回的还是对象,再调用toString方法。对象的toString方法默认返回[object object]
如果,调用对象的valueof方法直接返回一个原始类型的值,所以不再调用toString方法。
这里有一个特例,如果运算子是一个Date对象的实例,那么会优先执行toString方法。
上面代码中,对象obj
是一个Date
对象的实例,并且自定义了valueOf
方法和toString
方法,结果toString
方法优先执行。
21.余数运算符
余数运算符(%
)返回前一个运算子被后一个运算子除,所得的余数。
需要注意的是,运算结果的正负号由第一个运算子的正负号决定。
所以,为了得到负数的正确余数值,可以先使用绝对值函数。
22.自增和自减运算符
运算之后,变量的值发生变化,这种效应叫做运算的副作用(side effect)。自增和自减运算符是仅有的两个具有副作用的运算符,其他运算符都不会改变变量的值。
23.数值运算符,负数值运算符
数值运算符的作用在于可以将任何值转为数值(与Number
函数的作用相同)。
24.比较运算符
比较运算符用于比较两个值的大小,然后返回一个布尔值,表示是否满足执行的条件。
注意,比较运算符可以比较各种类型的值,不仅仅是数值。
八个比较运算符分为两类:相等比较和非相等比较。两者的规则是不一样的,对于非相等的比较,算法是先看两个运算子是否都是字符串,如果是的,就按照字典顺序比较(实际上是比较Unicode码点);否则,将两个运算子都转成数值,再比较数值的大小。
25.undefined 和 null
undefined
和null
与自身严格相等。
undefined
和null
只有与自身比较,或者互相比较时,才会返回true
;与其他类型的值比较时,结果都为false
。
26.相等运算符隐藏类型转换
27.取反运算符(!)
取反运算符是一个感叹号,用于将布尔值变为相反值,即true变为false,false变成true
对于非布尔值,取反运算符都会将其转为布尔值。可以这样记忆,除了以下六个值取反后为true,其他值都为false。
undefined、null、false、0、NaN、(空字符串)''
对一个值连续做两次取反运算,等于将其转为对应的布尔值,与Boolean函数的作用相同。
28.且运算符(&&)
且运算符(&&)往往用于多个表达式的求值。
它的运算规则是:如果第一个运算子的布尔值为true
,则返回第二个运算子的值(注意是值,不是布尔值);如果第一个运算子的布尔值为false
,则直接返回第一个运算子的值,且不再对第二个运算子求值。这种跳过第二个运算子的机制,被称为“短路”。
有些程序员喜欢用它取代if
结构,比如下面是一段if
结构的代码,就可以用且运算符改写。
if(i){ doSomething() } 等价于 i && doSomething()
且运算符可以多个连用,这时返回第一个布尔值为false
的表达式的值。如果所有表达式的布尔值都为true
,则返回最后一个表达式的值。
29.或运算(||)
或运算符(||
)也用于多个表达式的求值。它的运算规则是:如果第一个运算子的布尔值为true
,则返回第一个运算子的值,且不再对第二个运算子求值;如果第一个运算子的布尔值为false
,则返回第二个运算子的值。
或运算符可以多个连用,这时返回第一个布尔值为true
的表达式的值。如果所有表达式都为false
,则返回最后一个表达式的值。
或运算符常用于为一个变量设置默认值。
30.二进制位运算符
31.其他运算符,运算顺序
void
运算符的作用是执行一个表达式,然后不返回任何值,或者说返回undefined
。
<a href = 'javascript: void(f())' >文字< /a>
上述代码中,点击链接后,会先执行f()方法,由于返回值为undefined,所以不产生页面跳转。
32.逗号运算符
逗号运算符用于对两个表达式求值,并返回后一个表达式的值。
逗号运算符的一个用途是,在返回一个值之前,进行一些辅助操作。
var value = (console.log('Hi!'),true);
// Hi!
value //true
33.左结合与右结合
对于优先级别相同的运算符,同时出现的时候,就会有计算顺序的问题。
JavaScript语言的大多数运算符是“左结合”,少数运算符是“右结合”,其中最主要的是赋值运算符和三元条件运算符。
w = x = y = z => w = (x = (y = z))
q = a ? b : c ? d : e ? f : g => q = a ? b : (c ? d : (e ? f : g))
34.类型转换
强制转换主要指使用Number()
、String()
和Boolean()
三个函数,手动将各种类型的值,分别转换成数字、字符串或者布尔值。
(1)Number()
使用Number
函数,可以将任意类型的值转化成数值。
下面分成两种情况讨论,一种是参数是原始类型的值,另一种是参数是对象。
1)原始类型
2)Number
方法的参数是对象时,将返回NaN
,除非是包含单个数值的数组。
(2)String
函数可以将任意类型的值转化成字符串
(3)Boolean()
函数可以将任意类型的值转为布尔值。
除了以下五个值的转换结果为false
,其他的值全部为true.
undefined、null、0、NaN、''(空字符串)
注意:所用对象(包括空对象)的转换结果都是true。
35.自动转换
遇到以下三种个情况是,JavaScript会自动转换数据类型,即转换是自动完成的,用户不可见。
第一种情况,不同类型的数据互相运算。
第二种情况,对非布尔值类型的数据求布尔值。
第三种情况,对非数值类型的值使用一元运算符(即+
和-
)。
自动转换的规则是这样的:预期什么类型的值,就调用该类型的转换函数。比如,某个位置预期为字符串,就调用String()函数进行转换。如果该位置既可以是字符串,也可能是数值,那么默认转为数值。
由于子自动转换具有不确定行,而且不易出错,建议在预期为布尔值、数值、字符串的地方,全部使用Boolean()、Number()、String()函数进行显示转换。
36.Error实例对象
JavaScript解析或运行时,一旦发生错误,引擎就会抛出一个错误对象。JavaScript原生提供Error构造函数,所有抛出的错误都是这个构造函数的实例。
JavaScript语言值提到,error实例对象必须有message属性,表示出错时的提示信息,没有提到其他属性。
37.原生错误类型
Error实例对象时最一般的错误类型,在它的基础上,JavaScript还定义了其他6中错误对象。也就是说,存在Error的6个派生对象。
(1)SyntaxError对象
syntaxError对象时解析代码时发生的语法错误。
(2)ReferenceError对象
ReferenceError对象是引用一个不存在的变量时发生的错误。
另一种触发场景是,将一个值分配给无法分配的对象,比如对函数的运行结果赋值。
(3)RangeError对象
RangeError对象是一个值超出有效范围是发生的错误。
(4)TypeError对象
TypeError对象是变量或参数不是预期类型时发生的错误。比如,对字符串、布尔值、数值等原始类型的值使用new
命令,就会抛出这种错误,因为new
命令的参数应该是一个构造函数。
(5)URIError对象
URIError
对象是 URI 相关函数的参数不正确时抛出的错误,主要涉及encodeURI()
、decodeURI()
、encodeURIComponent()
、decodeURIComponent()
、escape()
和unescape()
这六个函数.
(6)EvalError对象
eval
函数没有被正确执行时,会抛出EvalError
错误。该错误类型已经不再使用了,只是为了保证与以前代码兼容,才继续保留。
38.throw语句
throw
语句的作用是手动中断程序执行,抛出一个错误。
39.编程风格
分号的自动添加,只有下一行的开始与本行的结尾,无法放在一起解释,JavaScript引擎才会自动添加分号。
另外,如果一行的起首是“自增”或“自减”运算符,则他们的前面会自动添加分号。
如果continue、break、return、throw这四个语句后面,直接分换行符,则会自动添加分号。
全局变量对于任何一个代码块,都是可读可写的,这对代码的模块化和重复使用,非常不利,可以考虑用大写字母表示变量名。
40.Object对象
JavaScript 的所有其他对象都继承自Object
对象,即那些对象都是Object
的实例。
Object
对象的原生方法分成两类:Object
本身的方法与Object
的实例方法。
Object
本身是一个函数,可以当作工具方法使用,将任意值转为对象。这个方法常用于保证某个值一定是对象。
如果参数为空(或者为undefined
和null
),Object()
返回一个空对象。
如果参数本身就是一个对象,则返回原该对象。
如果参数是各种原始类型的值,就返回该值
的包装对象。
instanceof
运算符用来验证,一个对象是否为指定的构造函数的实例。
41.Object的静态方法
Object.keys
方法和Object.getOwnPropertyNames
方法都用来遍历对象的属性。
Object.keys
方法的参数是一个对象,返回一个数组。该数组的成员都是该对象自身的(而不是继承的)所有属性名。
Object.getOwnPropertyNames
方法与Object.keys
类似,也是接受一个对象作为参数,返回一个数组,包含了该对象自身的所有属性名。
对于一般的对象来说,Object.keys()
和Object.getOwnPropertyNames()
返回的结果是一样的。只有涉及不可枚举属性时,才会有不一样的结果。Object.keys
方法只返回可枚举的属性,Object.getOwnPropertyNames
方法还返回不可枚举的属性名。
42.Object的实例方法
除了静态方法,还有不少方法定义在Object.prototype
对象。它们称为实例方法,所有Object
的实例对象都继承了这些方法。
Object.prototype.toString
方法返回对象的类型字符串,因此可以用来判断一个值的类型。
由于实例对象可能会自定义toString
方法,覆盖掉Object.prototype.toString
方法,所以为了得到类型字符串,最好直接使用Object.prototype.toString
方法。通过函数的call
方法,可以在任意值上调用这个方法,帮助我们判断这个值的类型。
Object.prototype.toString.call(value)
43.Object.defineProperty()
方法允许通过属性描述对象,定义或修改一个属性,然后返回修改后的对象
44.for...in循环、Object.keys方法、JSON.stringfy方法,当属性为不可枚举时,遍历不到。
另外,JSON.stringify
方法会排除enumerable
为false
的属性,有时可以利用这一点。如果对象的 JSON 格式输出要排除某些属性,就可以把这些属性的enumerable
设为false
。
45.configurable(描述对象中的元数据configurable)
configurable(可配置性)返回一个布尔值,决定了是否可以修改属性描述对象。也就是说,configurable
为false
时,writable
、enumerable
和configurable
都不能被修改了。
46.存取器。
除了直接定义以外,属性还可以用存取器(accessor)定义。其中,存值函数称为setter
,使用属性描述对象的set
属性;取值函数称为getter
,使用属性描述对象的get
属性。
一旦对目标属性定义了存取器,那么存取的时候,都将执行对应的函数。利用这个功能,可以实现许多高级特性,比如定制属性的读取和赋值行为。
47.对象的拷贝
需要注意 Object.getOwnPropertyDescriptor读不到继承属性的属性描述对象。
function copyObject(orig) {
return Object.create(
Object.getPrototypeOf(orig),
Object.getOwnPropertyDescriptors(orig)
);
}
48.数组
Array是JavaScript的原生对象,同时也是一个构造函数,可以用它生成新的数组。
var arr = new Array(2) arr.length //2
上面代码中,Array()
构造函数的参数2
,表示生成一个两个成员的数组,每个位置都是空值。
如果没有使用new
关键字,运行结果也是一样的。
考虑到语义性,以及与其他构造函数用法保持一致,建议总是加上new
。
49.数组静态方法
valueOf
方法是一个所有对象都拥有的方法,表示对该对象求值。不同对象的valueOf
方法不尽一致,数组的valueOf
方法返回数组本身。
toString
方法也是对象的通用方法,数组的toString
方法返回数组的字符串形式。
50.实例方法
sort()方法不是按照大小排序,而是按照字典顺序排序。也就是说,数值会被先转成字符产,再按照字典顺序进行比较。
如果想让sort
方法按照自定义方式排序,可以传入一个函数作为参数。
如果该函数的返回值大于0
,表示第一个成员排在第二个成员后面;其他情况下,都是第一个元素排在第二个元素前面。
map()方法将数组的所有成员一次传入参数函数,然后把每一次的执行结果组成一个新数组返回。
forEach()方法与map()方法相似,也是对数组的所有成员依次执行参数函数。但是,forEach()方法不返回值,只用来操作数据。
这就是说,如果数组遍历的目的是为了得到返回值,那么使用map()
方法,否则使用forEach()
方法。
some(),every() 这两个方法类似“断言”(assert),返回一个布尔值,表示判断数组成员是否符合某种条件。
它们接受一个函数作为参数,所有数组成员依次执行该函数。该函数接受三个参数:当前成员、当前位置和整个数组,然后返回一个布尔值
51.包装对象
所谓“包装对象”,指的是与数值、字符串、布尔值分别相对应的Number
、String
、Boolean
三个原生对象。这三个原生对象可以把原始类型的值变成(包装成)对象。
Number
、String
和Boolean
这三个原生对象,如果不作为构造函数调用(即调用时不加new
),而是作为普通函数调用,常常用于将任意类型的值转为数值、字符串和布尔值。
三种包装对象各自提供了许多实例方法,详见后文。这里介绍两种它们共同具有、从Object
对象继承的方法:valueOf()
和toString()
。
valueOf()
方法返回包装对象实例对应的原始类型的值。
toString()
方法返回对应的字符串形式。
某些场合,原始类型的值会自动当作包装对象调用,即调用包装对象的属性和方法。这时,JavaScript 引擎会自动将原始类型的值转为包装对象实例,并在使用后立刻销毁实例。
自动转换生成的包装对象是只读的,无法修改。所以,字符串无法添加新属性。
52.Math对象
Math.abs方法返回数值的绝对值。
Math.max()方法返回参数之中最大的那个值,Math.min返回最小的那个值。如果参数为空,Max.min返回Infinity,Max.max返回-Infinity。
Math.floor方法返回小于或等于参数值的最大整数(地板值)
Math.ceil方法返回大于或等于参数值的最小整数(天花板值)
这两个方法可以结合起来,实现一个总是返回数值的整数部分的函数。
Math.random()返回0到1之间的一个伪随机数,可能等于0,但是一定小于1
53.Date对象
Date对象可以作为普通函数直接调用,返回一个代表当前时间的字符串。
注意,即使带有参数,Date作为普通函数使用时,返回的还是当前时间。
Date还可以当作构造函数使用。对它使用new命令,会返回一个Date对象的实例。如果不加参数,实例代表的就是当前时间。
Date实例有一个独特的地方。其他对象求值的时候,都是默认调用.valueof()方法,但是Date实例求值的时候,默认调用的是toString()方法。这导致对Date实例求值,返回的是一个字符串,代表该实例对应的时间。
54.RegExp对象
正则表达式是一种表达式文本模式(即字符串结构)的方法,有点像字符串的模板,常常用来按照“给定模式”匹配文本。
新建正则表达式有两种方法。一种是使用字面量,以斜杠表示开始和结束。
var regex = /xyz/
另一种就是使用RegExp构造函数
var regex = new RegExp('xyz')
上面两种写法是等价的,都新建了一个内容为xyz的正则表达式对象。他们的主要区别是,第一种方法在引擎编译代码时,就会新建正则表达式,第二种方法在运行时新建正则表达式,所以前者的效率较高。
RegExp构造函数还可以接受第二个参数,表示修饰符
55.正则对象的实例属性
一类时修饰符相关,用于了解设置了什么修饰符。
另一类是与修饰符无关的属性,主要是下面两个。
RegExp.prototype.lastIndex
:返回一个整数,表示下一次开始搜索的位置。该属性可读写,但是只在进行连续搜索时有意义,详细介绍请看后文。RegExp.prototype.source
:返回正则表达式的字符串形式(不包括反斜杠),该属性只读。
56.正则对象的实例方法
正则实例对象的test方法返回一个布尔值,表示当前模式是否能匹配参数字符串。
/cat/.test(''cats and dogs") //true
正则实例对象的exec()
方法,用来返回匹配结果。如果发现匹配,就返回一个数组,成员是匹配成功的子字符串,否则返回null
。
57.JSON对象
JSON.stringify()
方法用于将一个值转为 JSON 字符串。该字符串符合 JSON 格式,并且可以被JSON.parse()
方法还原。
如果对象的属性是undefined、函数或xml对象,该属性会被JSON.stringify()过滤。
如果数组的成员是undefined、函数或xml对象,它们都被转成了null
。
JSON.stringify()
方法会忽略对象的不可遍历的属性
JSON.stringify()方法还可以接受一个数组,作为第二个参数,指定参数对象的哪些属性需要转成字符串。注意:这个类似白名单的数组,只对对象的属性有效,对数组无效。
第二个参数还可以是一个函数,用来更改JSON.stringify()的返回值。注意,这个处理函数是递归处理所有的键。
JSON.stringify()
还可以接受第三个参数,用于增加返回的 JSON 字符串的可读性。
58.JSON对象-参数对象的toJSON()方法
如果参数对象有自定义的toJSON()方法,那么JSON.stringify()会使用这个方法的返回值作为参数,而忽略原对象的其他属性。
Date
对象就有一个自己的toJSON()
方法。
59.JSON.parse()
方法可以接受一个处理函数,作为第二个参数,用法与JSON.stringify()
方法类似。
60.对象
JavaScript 语言的对象体系,不是基于“类”的,而是基于构造函数(constructor)和原型链(prototype)。
JavaScript 语言使用构造函数(constructor)作为对象的模板。所谓”构造函数”,就是专门用来生成实例对象的函数。它就是对象的模板,描述实例对象的基本结构。一个构造函数,可以生成多个实例对象,这些实例对象都有相同的结构。
构造函数的特点有两个。
- 函数体内部使用了
this
关键字,代表了所要生成的对象实例。 - 生成对象的时候,必须使用
new
命令。
使用new
命令时,根据需要,构造函数也可以接受参数。
new
命令本身就可以执行构造函数,所以后面的构造函数可以带括号,也可以不带括号。下面两行代码是等价的,但是为了表示这里是函数调用,推荐使用括号。
使用new
命令时,它后面的函数依次执行下面的步骤。
- 创建一个空对象,作为将要返回的对象实例。
- 将这个空对象的原型,指向构造函数的
prototype
属性。 - 将这个空对象赋值给函数内部的
this
关键字。 - 开始执行构造函数内部的代码。
如果构造函数内部有return
语句,而且return
后面跟着一个对象,new
命令会返回return
语句指定的对象;否则,就会不管return
语句,返回this
对象。
61.Object.create()创建实例对象
构造函数作为模板,可以生成实例对象。但是,有时拿不到构造函数,只能拿到一个现有的对象。我们希望以这个现有的对象作为模板,生成新的实例对象,这时就可以使用Object.create()
方法。新生成对象继承了模板的属性和方法。
const obj2 = Object.create(obj)
//新生成的对象是一个空对象,但这个对象的(__proto__)原型链指向obj,即新生对象继承了模板的属性和方法。
62.this
this不管使用在什么场合,this都有一个共同点:它总是返回一个对象。
简单说,this就是属性或方法“当前”所在的对象。
this.property this就代表property属性当前所在的对象。
①this可以用在构造函数之中,表示实例对象,
63.Javascript继承机制
JavaScript 继承机制的设计思想就是,原型对象的所有属性和方法,都能被实例对象共享。也就是说,如果属性和方法定义在原型上,那么所有实例对象就能共享,不仅节省了内存,还体现了实例对象之间的联系。
JavaScript 规定,每个函数都有一个prototype
属性,指向一个对象。
对于普通函数来说,该属性基本无用。但是,对于构造函数来说,生成实例的时候,该属性会自动成为实例对象的原型。
64.instanceof运算符
instanceof
运算符返回一个布尔值,表示对象是否为某个构造函数的实例。
var v = new Vehicle()
v instanceof Vehicle //true
instanceof
运算符的左边是实例对象,右边是构造函数。它会检查右边构造函数的原型对象(prototype),是否在左边对象的原型链上。因此,下面两种写法是等价的。
v instanceof Vehicle 等同于 Vehicle.prototype.isPrototypeof(v)
上面代码中,Vehicle
是对象v
的构造函数,它的原型对象是Vehicle.prototype
,isPrototypeOf()
方法是 JavaScript 提供的原生方法,用于检查某个对象是否为另一个对象的原型,
65.Object对象的相关方法
Object.getPrototypeOf()方法返回参数对象的原型。这是获取原型对象的标准方法。
Object.setPrototypeOf()方法为参数对象设置原型,返回该参数对象。它接受两个参数,第一个是现有对象,第二个是原型对象。
66.浏览器环境
JavaScript是浏览器的内置脚本语言。也就是说,浏览器内置了JavaScript引擎,并且提供各种接口,让JavaScript脚本可以控制浏览器的各种功能。一旦网页内嵌了JavaScript脚本,浏览器加载网页,就会去执行脚本,从而达到操作浏览器的目的,实现网页的各种动态效果。
(1)代码嵌入网页的方法
1)script元素嵌入代码
2)script元素加载外部脚本
3)事件属性:即在元素上绑定事件,当事件发生时,调用回调方法。
4)url协议:url支持javascript:协议,即在url的位置写入代码,使用这个url的时候就会执行JavaScript代码。如果JavaScript代码返回一个字符串,浏览器就会新建一个文档,展示i这个字符串的内容,原有文档的内容都会消失。如果返回的不是字符串,那么浏览器不会新建文档,也不会跳转。
67.script元素
工作原理:
浏览器加载JavaScript脚本,主要通过<script>元素完成。正常的网页加载流程是这样的。
(1).浏览器一边下载HTMl网页,一边开始解析。也就是说,不等到下载完,就开始解析。
(2).解析过程中,浏览器发现<script>元素,就暂停解析,把网页渲染的控制权转交给JavaScript引擎。
(3).如果<script>元素引用了外部脚本,就下载再执行,否则就直接执行代码。
(4).JavaScript引擎执行完毕,控制权交换渲染引擎,恢复往下解析HTML网页。
加载外部脚本时,浏览器会暂停页面渲染,等待脚本下载并执行完成后,再继续渲染。原因是 JavaScript 代码可以修改 DOM,所以必须把控制权让给它,否则会导致复杂的线程竞赛的问题。
如果外部脚本加载时间很长(一直无法完成下载),那么浏览器就会一直等待脚本下载完成,造成网页长时间失去响应,浏览器就会呈现“假死”状态,这被称为“阻塞效应”。
为了避免这种情况,较好的做法是将<script>
标签都放在页面底部,而不是头部。这样即使遇到脚本失去响应,网页主体的渲染也已经完成了,用户至少可以看到内容,而不是面对一张空白的页面。
如果某些脚本代码非常重要,一定要放在页面头部的话,最好直接将代码写入页面,而不是连接外部脚本文件,这样能缩短加载时间。
脚本文件都放在网页尾部加载,还有一个好处。因为在 DOM 结构生成之前就调用 DOM 节点,JavaScript 会报错,如果脚本都在网页尾部加载,就不存在这个问题,因为这时 DOM 肯定已经生成了。
一种解决方法是设定DOMContentLoaded
事件的回调函数。
DOMContentLoaded
事件只有在 DOM 结构生成之后才会触发。
使用<script>
标签的onload
属性。当<script>
标签指定的外部脚本文件下载和解析完成,会触发一个load
事件,可以把所需执行的代码,放在这个事件的回调函数里面。
如果有多个script
标签,比如下面这样。
<script src='a.js'></script>
<script src='b.js'></script>
浏览器会同时并行下载a.js
和b.js
,但是,执行时会保证先执行a.js
,然后再执行b.js
,即使后者先下载完成,也是如此。
解析和执行 CSS,也会产生阻塞。Firefox 浏览器会等到脚本前面的所有样式表,都下载并解析完,再执行脚本;Webkit则是一旦发现脚本引用了样式,就会暂停执行脚本,等到样式表下载并解析完,再恢复执行。
68.<script>
元素的defer
属性
为了解决脚本文件下载阻塞网页渲染的问题,一个方法是对<script>
元素加入defer
属性。它的作用是延迟脚本的执行,等到 DOM 加载生成后,再执行脚本。
defer
属性的运行流程如下。
- 浏览器开始解析 HTML 网页。
- 解析过程中,发现带有
defer
属性的<script>
元素。 - 浏览器继续往下解析 HTML 网页,同时并行下载
<script>
元素加载的外部脚本。 - 浏览器完成解析 HTML 网页,此时再回过头执行已经下载完成的脚本。
有了defer
属性,浏览器下载脚本文件的时候,不会阻塞页面渲染。下载的脚本文件在DOMContentLoaded
事件触发前执行(即刚刚读取完</html>
标签),而且可以保证执行顺序就是它们在页面上出现的顺序。
对于内置而不是加载外部脚本的script
标签,以及动态生成的script
标签,defer
属性不起作用。另外,使用defer
加载的外部脚本不应该使用document.write
方法。
69.<script>
元素的async属性
解决“阻塞效应”的另一个方法是对<script>
元素加入async
属性。
async
属性的作用是,使用另一个进程下载脚本,下载时不会阻塞渲染。
- 浏览器开始解析 HTML 网页。
- 解析过程中,发现带有
async
属性的script
标签。 - 浏览器继续往下解析 HTML 网页,同时并行下载
<script>
标签中的外部脚本。 - 脚本下载完成,浏览器暂停解析 HTML 网页,开始执行下载的脚本。
- 脚本执行完毕,浏览器恢复解析 HTML 网页。
async
属性可以保证脚本下载的同时,浏览器继续渲染。需要注意的是,一旦采用这个属性,就无法保证脚本的执行顺序。哪个脚本先下载结束,就先执行那个脚本。另外,使用async
属性的脚本文件里面的代码,不应该使用document.write
方法。
一般来说,如果脚本之间没有依赖关系,就使用async
属性,如果脚本之间有依赖关系,就使用defer
属性。如果同时使用async
和defer
属性,后者不起作用,浏览器行为由async
属性决定。
70.script加载使用的协议
如果不指定协议,浏览器默认采用 HTTP 协议下载。
如果要采用 HTTPS 协议下载,必需写明。
71.浏览器的组成
浏览器的核心是两部分:渲染引擎和 JavaScript 解释器(又称 JavaScript 引擎)。
渲染引擎:渲染引擎的主要作用是,将网页代码渲染为用户视觉可以感知的平面文档。
渲染引擎处理网页,通常分成四个阶段。
- 解析代码:HTML 代码解析为 DOM,CSS 代码解析为 CSSOM(CSS Object Model)。
- 对象合成:将 DOM 和 CSSOM 合成一棵渲染树(render tree)。
- 布局:计算出渲染树的布局(layout)。
- 绘制:将渲染树绘制到屏幕。
以上四步并非严格按顺序执行,往往第一步还没完成,第二步和第三步就已经开始了。所以,会看到这种情况:网页的 HTML 代码还没下载完,但浏览器已经显示出内容了。