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 代码还没下载完,但浏览器已经显示出内容了。

浙公网安备 33010602011771号