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命令删除对象objp属性。删除后,再读取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 

undefinednull与自身严格相等。

undefinednull只有与自身比较,或者互相比较时,才会返回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本身是一个函数,可以当作工具方法使用,将任意值转为对象。这个方法常用于保证某个值一定是对象。

如果参数为空(或者为undefinednull),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方法会排除enumerablefalse的属性,有时可以利用这一点。如果对象的 JSON 格式输出要排除某些属性,就可以把这些属性的enumerable设为false

45.configurable(描述对象中的元数据configurable)

configurable(可配置性)返回一个布尔值,决定了是否可以修改属性描述对象。也就是说,configurablefalse时,writableenumerableconfigurable都不能被修改了。

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.包装对象

所谓“包装对象”,指的是与数值、字符串、布尔值分别相对应的NumberStringBoolean三个原生对象。这三个原生对象可以把原始类型的值变成(包装成)对象。

NumberStringBoolean这三个原生对象,如果不作为构造函数调用(即调用时不加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命令时,它后面的函数依次执行下面的步骤。

  1. 创建一个空对象,作为将要返回的对象实例。
  2. 将这个空对象的原型,指向构造函数的prototype属性。
  3. 将这个空对象赋值给函数内部的this关键字。
  4. 开始执行构造函数内部的代码。

如果构造函数内部有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.prototypeisPrototypeOf()方法是 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.jsb.js,但是,执行时会保证先执行a.js,然后再执行b.js,即使后者先下载完成,也是如此。

 

解析和执行 CSS,也会产生阻塞。Firefox 浏览器会等到脚本前面的所有样式表,都下载并解析完,再执行脚本;Webkit则是一旦发现脚本引用了样式,就会暂停执行脚本,等到样式表下载并解析完,再恢复执行。

68.<script>元素的defer属性

为了解决脚本文件下载阻塞网页渲染的问题,一个方法是对<script>元素加入defer属性。它的作用是延迟脚本的执行,等到 DOM 加载生成后,再执行脚本。

defer属性的运行流程如下。

  1. 浏览器开始解析 HTML 网页。
  2. 解析过程中,发现带有defer属性的<script>元素。
  3. 浏览器继续往下解析 HTML 网页,同时并行下载<script>元素加载的外部脚本。
  4. 浏览器完成解析 HTML 网页,此时再回过头执行已经下载完成的脚本。

有了defer属性,浏览器下载脚本文件的时候,不会阻塞页面渲染。下载的脚本文件在DOMContentLoaded事件触发前执行(即刚刚读取完</html>标签),而且可以保证执行顺序就是它们在页面上出现的顺序。

对于内置而不是加载外部脚本的script标签,以及动态生成的script标签,defer属性不起作用。另外,使用defer加载的外部脚本不应该使用document.write方法。

69.<script>元素的async属性

解决“阻塞效应”的另一个方法是对<script>元素加入async属性。

async属性的作用是,使用另一个进程下载脚本,下载时不会阻塞渲染。

  1. 浏览器开始解析 HTML 网页。
  2. 解析过程中,发现带有async属性的script标签。
  3. 浏览器继续往下解析 HTML 网页,同时并行下载<script>标签中的外部脚本。
  4. 脚本下载完成,浏览器暂停解析 HTML 网页,开始执行下载的脚本。
  5. 脚本执行完毕,浏览器恢复解析 HTML 网页。

async属性可以保证脚本下载的同时,浏览器继续渲染。需要注意的是,一旦采用这个属性,就无法保证脚本的执行顺序。哪个脚本先下载结束,就先执行那个脚本。另外,使用async属性的脚本文件里面的代码,不应该使用document.write方法。

一般来说,如果脚本之间没有依赖关系,就使用async属性,如果脚本之间有依赖关系,就使用defer属性。如果同时使用asyncdefer属性,后者不起作用,浏览器行为由async属性决定。

70.script加载使用的协议

如果不指定协议,浏览器默认采用 HTTP 协议下载。

如果要采用 HTTPS 协议下载,必需写明。

71.浏览器的组成

浏览器的核心是两部分:渲染引擎和 JavaScript 解释器(又称 JavaScript 引擎)。

渲染引擎:渲染引擎的主要作用是,将网页代码渲染为用户视觉可以感知的平面文档。

渲染引擎处理网页,通常分成四个阶段。

  1. 解析代码:HTML 代码解析为 DOM,CSS 代码解析为 CSSOM(CSS Object Model)。
  2. 对象合成:将 DOM 和 CSSOM 合成一棵渲染树(render tree)。
  3. 布局:计算出渲染树的布局(layout)。
  4. 绘制:将渲染树绘制到屏幕。

以上四步并非严格按顺序执行,往往第一步还没完成,第二步和第三步就已经开始了。所以,会看到这种情况:网页的 HTML 代码还没下载完,但浏览器已经显示出内容了。

posted @ 2021-12-09 09:15  亦茫茫  阅读(41)  评论(0编辑  收藏  举报