⭐️ 本文首发自 前端修罗场(点击加入),是一个由 资深开发者 独立运行 的专业技术社区,我专注 Web 技术、Web3、区块链、答疑解惑、面试辅导以及职业发展博主创作的 《前端面试复习笔记》(点击订阅),广受好评,已帮助多人提升实力、拿到 offer。现在订阅,私聊我即可获取一次免费的模拟面试机会,帮你评估知识点的掌握程度,获得更全面的学习指导意见!

javascrpt高级教程

一个完整的javascript实现,应该包括三部分:

  • (1)ECMAScript(核心)
  • (2) DOM(文档对象模型)
  • (3)BOM(浏览器对象模型)

一、 ECMAScript兼容:

  • (1)支持ECMA-262描述的所有“类型、值、对象、属性、函数以及程序句法和语法”;
  • (2)支持Unicode字符标准;
  • (3)添加ECMA-262没有描述的“更多类型、值、对象、属性、函数”;
  • (4)支持ECMA-262没有定义的“程序和正则表达式语法”。
    基于以上要求,为开发人员基于ECMAScript开发一门新语言提供了广阔的空间和极大的灵活性。这也是ECMAScript受开发人员欢迎的原因。

二、DOM——API

DOM是针对XML但是经过扩展用于HTML的应用程序编程接口。

DOM把整个页面映射为一个多层节点结构。通过DOM创建的表示文档的树形图,开发者能够获得控制页面内容和结构的主动权。借助DOM提供的API,我们就可以轻松的删除、添加、替换或修改任何节点。

<script>标签属性

async:立即下载脚本,不妨碍页面中其他操作。只对外部脚本文件有效。

  • 注释:
    标记为async的脚本并不保证按照指定它们的先后顺序执行。
    如:
<script async src="a.js"></script>
<script async src="b.js"></script>

b.js可能会在a.js文件之前执行。
因此,确保两者之间互不依赖非常重要。
建议异步脚本不要再加载期间修改DOM。
异步脚本一定会在页面的load事件前执行,但可能会在 DOMContentLoaded事件触发之前或之后执行。

defer:脚本可延迟到文档完全被解析和显示之后再执行。相当于告诉浏览器——立即下载,但延迟执行。只对外部脚本有效。

注释:无论如何包含代码,只要不存在defer和async属性,浏览器都会按照

type:虽然text/javascript和text/ecmascript都已经不被推荐使用,但人们一直以来还是约定俗成地使用text/javascript。
实际上,服务器在传送javascript文件时使用的MIME类型通常是application/x-javascript,但在type中设置这个值却可能导致脚本被忽略。

考虑到约定俗成和最大限度的浏览器兼容性,目前type属性的值还是text/javascript。如果不写type,默认值也是这个。

注意:不要在脚本中任何语句中出现 字符串,否则会被浏览器认为该段脚本的结束。如果要出现,请通过转义字符解决。 如:

<script type="text/javascript">
function sayScript(){
alert("<\/script>");
}
</script>

同样,在解析外部javascript文件时,页面的处理也会暂时停止。

如果是在XHTML文档中,也可以省略前面示例代码中结束的</script>标签,直接使用<script src="" type=""/>

但是在html文档中,不是使用该方法。
原因:这张语法不符合HTML规范,而且也得不到某些浏览器(尤其是IE)的正确解析。

有一点:按照惯例,外部javascript文件带有js扩展名。这个扩展名不是必需的,因为浏览器不会检查包含javascript的文件的扩展名。但是,服务器通常还是看扩展名决定为响应应用哪种MIME类型。如果不实用.js扩展名,请确保服务器返回正确的MIME类型。

三、XHMLT模式

在XHTML中,会出现一些解析错误。但是这些解析能做html中执行。为了避免这样的错误,例如:在XHTML中,小于号( < )会被当做开始一个新标签来解析。那么通常避免这样错误的方法是:
使用CData片段:即使用CData片段来包含javascript代码。CData片段是文档中的一个特殊区域,这个区域中可以包含不需要解析的任意格式的文本内容。
但是,有些浏览器并不支持CData,那么我们就需要将CData标记进行注释即可。
例如:

<script>
//< ! [CDATA[
function  compare(a,b) {
//
}
//]]>
< /script>

这种格式在所有现代浏览器中都可以正常使用。

文档模式:分为混杂模式(不推荐使用)和标准模式。

用在不支持javascript的浏览器中显示替代内容。这个元素可以包含能够出现在文档中得任何HTML元素,

内容只有在下列条件下才会显示出来:
(1)浏览器不支持脚本;
(2)浏览器支持脚本,但脚本本禁用。

例如:

本页面需要了浏览器启用javascript

语法

  • 1、区分大小写;
  • 2、函数名不能使用typeof,但typeof完全可以是一个有效的函数名;
  • 3、标识符的命名:
    标识符,即变量、函数、属性的名字。或者函数的参数。
    ES规范中,采用“驼峰”命名方式——即第一个字母小写,剩下的每个单词的首字母大写,为了与ES内置的函数和对象命名格式保持一致,推荐使用该方式。
  • 4、严格模式:
    由ES5引入,是为js定义了一种不同的解析与执行模型。在该模式下,ES3的一些不确定的行为将得到处理。而且对某些不安全的操作也会抛出错误。

若要在整个脚本中使用严格模式,可再脚本顶部增加:

"use strict";

这是一个编译指示,用于告诉支持的js引擎切换到严格模式。这是为不破坏ES3语法而特意选定的语法。

严格模式下,javascript的执行结果会有很大不同。

  • 5、分号与代码块
  1. 分号虽然不是必须得,但是建议任何语句的结束都要使用它。
    因为加上分号,可以避免很多错误,例如“压缩”。
    另外,加上分号可再某些情况下增进代码的性能,因为这样解析器不必花时间推测应该在哪里插入分号了。
  2. 代码块,即“{}”花括号。在控制语句中使用代码块,可以让编码意图更加清晰,也能降低修改代码时出错的几率。
  • 6、变量
  1. ES中,变量是松散类型——即,可以用来保存任何类型的数据。简言之,每个变量仅仅是一个用于保存值的占位符。
  2. 【需要注意】用var定义的变量将成为定义该变量的作用域中的局部变量——即,在函数中使用var定义一个变量,那么这个变量在函数退出后就会被销毁。
  3. 如果省略了var,变量就会变成全局变量——不推荐,在严格模式下,会导致ReferenceError错误;
  4. 在严格模式下,不能定义名为eval或arguments的 变量,否则会导致语法错误;
  • 7、数据类型
  1. 5中数据类型:undefined\null\Boolean\Number\String\Object
  2. typeof 是操作符,而非函数。typeof null会返回object,因为特殊值null被认为是一个空得对象引用,有些浏览器也会返回function.
  3. 从技术角度,function在ES中是对象,不是一种数据类型。
  4. 对未初始化的变量执行typeof操作符返回undefined,而对未声明的变量执行typeof操作符同样返回undefined。
  5. null值表示一个空对象指针。如果定义的变量准备在将来用于保存对象,那么最好将该变量初始化为null,而不是其他值。
    实际上,undefined派生自null,但两者的用途完全不同。
  6. Boolean类型的值有2个,true和false,区分大小写。Boolean()可用于转换;
  7. NaN:涉及NaN的操作都会返回NaN,其次,NaN与任何值都不相等,包括NaN本身;isNaN()用于检测
  8. 数值转换:Number(),parseInt(),parseFloat();
    parseInt()会忽略字符串前面的空格,直到找到第一个非空格字符;
    parseInt()转换空字符串——返回NaN;
    parseFloat()只解析十进制;
  9. 按位非:由(~)波浪号表示,例如:
var num1=25;
var num2=~num1-1;//num2=-26

本质:操作数的负值减1。

  1. 逻辑非:用(!)表示,
!Object //false;
!NaN //true;
!underfined//true
  1. 全等(===)与相等:
var result1=("55"==55);//true,转换后相等
var result2=("55"===55);//false,不经转换,不同的数据类型不相等;

区别:
全等——在比较前,不对数据进行转换;
相等——在比较前,对数据进行转换;

牢记:

null==underfined;//true,因为它们是类似的值;
null===undefined;//false,因为他们是不用类型的值;

Tip:由于相等于不等操作符存在类型转换问题,而为了保持代码中数据类型的完整性,推荐使用全等和不全等操作符。

  1. for-in
    for-in是一种精准的迭代语句。可以用来枚举对象的属性;
    语法:
for(property propName in window)

例如:

for(var propName in window){console.log(propName);}

注释:我们使用for-in循环来显示BOM中window对象的所有属性。每次执行循环都会将window对象中存在的一个属性名赋值为变量propName。这个过程直到对象中所有属性都被枚举一遍为止。

ES中,对象的属性没有顺序,因此for-in循环输出的属性的顺序不可测。

如果表示迭代的对象的变量值为null或underfined,for-in语句会抛出错误。虽然ES5更正了这一点,进行不执行循环体处理,但是为了兼容性,在使用之前,先检测确认该对象的值是否是null或underfined。

  1. with()
    with语句的作用是将代码的作用域设置到一个特定的对象上;
    作用:简化多次编写同一个对象的工作;
    例如:
var qs=location.search.substring(1);
var hostname=location.hostname;
var url=location.href;
//使用with对上述代码更改:
with(location){var qs=search.substring(1);var hostname=hostname;var url=href;
}
//即,location是同一个对象

注意:

(1)在with语句中,每个变量被认为是一个局部变量,而如果在局部变量环境中找不到该变量的定义,就会查询location对象中是否有同名的属性。如果有同名属性,则以location对象属性的值作为变量的值。
(2)在严格模式下,不允许使用with语句,否则视为语法错误。
(3)使用with会导致性能下降,也给调式带来困难。

  1. switch()
    switch语句在比较值时使用的是全等运算符,因此不会出现类型转换问题。

  2. 函数-封装语句

  • ES中得函数在定义时不必指定是否返回值;
    实际上,任何函数在任何时候都可以通过return语句后跟要返回的值来实现返回值。如:return num1+num2;
  • 函数在执行完return语句后停止并会立即退出;因此,位于Return之后的语句是永远不会被执行的;
  • return语句可以不带任何返回值。在这种情况下,函数在停止执行后将返回underfined值。这种用法一般用在需要提前停止函数执行而又不需要返回值的情况下。
    推荐的做法:要么让函数始终有返回值,要么都不要有返回值。否则,如果时有时没有,这会给调试代码带来不便。

严格模式下,对函数有一些规则:

  • 把函数命名为eval或arguments;
  • 不能把参数命名为eval或arguments;
  • 不能出现两个命名参数同名的情况 ;
    出现上述情况,会导致代码无法执行。
  1. 参数
  • ES中不介意传递进来多少个参数,不在乎传进来的参数是什么类型;

  • 即便你定义的函数只接收两个参数,在调用这个函数时也未必一定要传递两个参数,可以传递一个、三个甚至不传,解析器不会有什么怨言的;原因——ES中参数在内部是用一个数组来表示的,函数接收到的始终是这个数组,而不关心数组中包含哪些参数。

    • 实际上,在函数体内可通过arguments对象来访问这个参数数组,从而获取传递给函数的每一个参数,如arguments[0]表示第一个参数。

  • ES函数的一个重要特点:命名的参数只提供便利,但不是必须的;

  • 通过arguments的length属性能够获知有多少个参数传递给了函数;

  • arguments对象可以与命名参数一起使用。

  • arguments的值永远与对应命名参数的值保持同步,但是它们的内存空间是独立的。
    • 如果只传入一个参数,那么为argument[1]设置的值不会反应到命名参数中。因为arguments对象的长度是由传入的参数个数决定的,不是由定义函数时的命名参数的个人决定的;

  • 没有传递值的命名参数将自动被赋予underfined值。

  • 严格模式下,对arguments的值进行重写,会导致错误;

function doAdd(num1,num2) {arguments[1]=10;//在严格模式下,这样的重写会导致错误;
}
  1. ES函数没有重载

因为ES函数没有签名(接收参数的类型和数量),其参数是由包含零或多个值的数组来表示的。而没有函数签名,真正的重载是不能做到的。

模仿重载——通过检查传入函数中参数的类型和数量并作出不同的反应;

例如:

function doAdd() {if(arguments.length==1){//}else if(arguments.length==2){//}
}

四、变量、作用域和内存问题

基本类型和引用类型

  • 基本类型的值是简单的数据段,引用类型的值是由多个值构成的对象;
  • 在将一个值赋给变量时,解析器必须确定这个值是基本类型还是引用类型;
  • 引用类型的值是保存在内存中得对象。javascript不允许直接访问内存中得位置,不能直接操作对象的内存空间。在操作对象时,实际上我们操作的是对象的引用。
  • javascript中字符串不是引用类型;

创建对象:

var person=new Object();
person.name="jack";//添加name 属性
//如果对象不被销毁或者属性不被删除,这个属性会一直存在

与基本类型不同的是,在赋值的时候,引用类型的变量的值实际上是一个指针,这个指针指向存储在堆栈中的一个对象。赋值操作结束后,两个变量实际上将引用同一个对象。改变其中一个变量的值将影响到另一个变量的值。

例子:

function setName(obj){obj.name="jack";
}
var person=new Object();
setName(person);
alert(person.name);//jack//对比下面
function setName(obj){obj.name="jack";
obj=new Object;
obj.name="mick";
}
var person=new Object();
setName(person);
alert(person.name);//"jack"

说明:在第2段代码中,即使在函数内部修改参数的值,但原始的引用仍然保持不变。实际上,当在函数内部重写obj时,这个变量引用的就是一个局部对象。而这个局部对象会在函数执行完毕后立即被销毁。

检测引用类型

在数值类型中,使用typeof能够很好检测数值变量的类型,但是对于引用类型的变量,typeof就不行了。

使用instanceof来检测引用类型的变量,如果是引用类型,那么返回true。如果是数值类型,返回false。
例如:

alert(person instanceof Object) //变量person是Object吗

五、执行环境及作用域

  • 执行环境(环境),是js中最重要的概念。
  • 执行环境定义了变量或函数有权访问的其他数据。每个执行环境都有一个与之关联的变量对象。
  • 环境中定义的所有变量和函数都保存在这个对象中。
  • 全局执行环境是最外围的一个执行环境。在web浏览器中,全局执行环境是window对象,因此所有全局变量和函数都是作为window对象的属性和方法创建。
  • 某个执行环境中得所有代码执行完毕,该环境就会被销毁。例如,浏览器关闭,网页关闭;
  • 执行环境的产生,是通过执行流。当执行流进入一个函数时,函数的环境就会被推入一个环境栈中。函数执行完毕后,就把环境弹出。
  • 当代码在一个环境中执行时,会创建变量对象的一个作用域链。作用域链的用途,是保证对执行环境有权访问的所有变量和函数的有序访问。
  • 全局执行环境的变量对象始终都是作用域链中的最后一个对象。

(1)延长作用域链

当执行流进入下列任何一个语句时,作用域链就会加长;

  • try-catch的catch块;
  • with()语句;

(2)没有块级作用域

  • 在js中,由for语句创建的变量i,即使在for循环执行结束后,也依旧会存在于循环体外部的执行环境中。
例如:for(var i=0;i<10;i++){}
console.log(i);//10
  • 在函数内部,最接近的环境就是函数的局部环境;
  • 在with语句中,最接近的环境是函数环境;
  • 如果初始化变量时没有使用var声明,那么该变量会自动被添加到全局环境中;但是,不使用var声明变量是错误的做法,在严格模式下,会报错。
  • 查询标示符:向上查询,查询到就结束。在搜索过程中,如果存在一个局部变量的定义,则搜索会自动停止,不再进入另一个变量对象。如果局部环境中存在着同名标识符,就不会使用位于父环境中的标识符。
  • 在函数内部,可以使用window.变量名访问全局变量;

(3)函数的属性与方法


(1)在js中,一个函数可以作为另一个函数接收的一个参数。
我们可以先定义一个函数,然后传递,也可以在传递参数的地方直接定义函数。
如:

function execute(someFunction ,value){someFunction(value);
}
execute(function(word){console.log(word)},"Hello");

函数内部另一个特俗对象是this,this引用的是函数据以执行的环境对象。当在网页的全局作用域中调用函数时,this对象引用的就是window.

记住:函数的名字仅仅是一个包含指针的变量而已。

在ES5中规范化了另一个函数对象的属性:caller,这个属性中保存着调用当前函数的函数的引用,如果是在全局作用域中调用当前函数,它的值为null。
例如:

function outer(){ inner();
}
function inner(){alert(inner.caller);
}
outer();

结果:警告框中显示outer()函数的源代码;因为outer()调用了inter(),所以inner.caller就指向outer()

函数的属性和方法:

  • (1)每个函数都包含两个属性:length和prototype
    其中,length属性表示函数希望接收的命名参数的个数;
    prototype属性:保存它们所有实例方法的真正所在。例如,toString()和valueOf()等方法实际上都保存在prototype名下,只不过是通过各自对象的实例访问罢了。在创建自定义引用类型以及实现继承时,prototype属性的作用极为重要。在ES5中prototype属性不可枚举,因此使用for-in无法实现。

  • (2)每个函数都包含两个非继承而来的方法:apply()和call()
    apply()方法接收两个参数:一个是在其中运行函数的作用域,另一个是参数数组(这可以是Array的实例,也可以是arguments对象)。

例如:

function callSum1(num1,num2){return sum.apply(this,arguments);//或者return sum.apply(this,[num1,num2]);
}
function sum(num1,num2){return num1+num2;
}

call()与apply()作用相同,区别仅在于接收参数的方式不同。对于call,第一个参数是this没有变化,变化的是其余参数都直接传递给函数。——在使用call()方法时,传递给函数的参数必须逐个列举出来。
例如:

function callSum(num1,num2){return sum.call(this,num1,num2);
}```javascript

使用call()还是apply()取决于你采取哪种给函数传递参数的方式最方便;

apply()与call()真正的用武之地是能够扩充函数赖以运行的作用域。bind():该方法会创建一个函数实例,其this值会被绑定到传给bind()函数的值。

注意:对类型进行判定最佳的方式是通过

Object.prototype.toString.call(数据)

例如:

Object.prototype.toString.call([])=='[Object Array]'
Object.prototype.toString.call("abc")=='[Object String]'

六、垃圾收集

Javascript具有自动垃圾收集机制。在编写javascript程序时,开发人员不用再关心内存使用问题,所需内存的分配以及无用内存的回收完全实现了自动管理。
原理 :找出那些不再继续使用的变量,然后释放其占用的内存。为此,垃圾收集器会按照固定的时间间隔(或代码执行中预定的收集时间),周期性地执行这一操作。

垃圾收集器必须跟踪哪个变量有用哪个变量没用,对于不再有用的变量打上标记,以备将来收回其占用的内存。用于编辑无用变量的策略可能会因实现而异,但具体到浏览器中的实现,则通常有两个策略:

(1)标记清除

这是各浏览器最常用的方式。可使用任何方式来标记变量。比如,可通过翻转某个特殊的位来记录一个变量何时进入环境,或者使用一个“进入环境的”变量列表及一个“离开环境的”变量列表来跟踪哪个变量发生了变化。说到底,如何标记变量其实并不重要,关键在于采取什么策略。

(2)引用计数

这是一种不常见的方式。引用计数的含义:跟踪记录每个值被引用的次数。当声明一个变量并将一个引用类型值赋给该变量时,则这个值的引用次数就是1.如果同一个值又被赋给另一个变量,则该值的引用次数加1。相反,如果包含对这个值引用的变量又取得另一个值,则这个值的引用次数减1。当这个值的引用次数变成0时,则说明没有办法再访问这个值了,因而就可以将其占用的内存空间回收回来。

但是,引用计数会遇到一个严重的问题:循环引用——指的是对象A中包含一个指向对象B的指针,而对象B中也包含一个指向对象A的引用。

(3)性能问题
说到垃圾收集器多长时间运行一次,不禁让人联想到IE因此而声名狼藉的性能问题。IE的垃圾收集器是根据内存分配量运行的,具体一点就是256个变量、4096个对象或数组字面量和数组元素或者64KB的字符串。达到上述任何一个临界值,垃圾收集器就会运行。

这种实现方式的问题在于,如果一个脚本中包含那么多变量,那么该脚本很可能会在其生命周期中一直保有那么多的变量。这样一来,垃圾收集器就不得不频繁地运行。结果,由此引发的严重性能问题促使IE7重写了其垃圾收集例程。
在IE7中的垃圾收集机制有所改变的是:如果垃圾收集例程回收的内存分配量低于15%,则变量、字面量和(或)数组元素的临界值就会加倍。如果例程回收了85%的内存分配量,则将各种临界值重置回默认值。这看似简单的调整,却极大提升IE在运行包含大量javascript的页面时的性能。

(4)管理内存

javascript中内存管理面临的最主要的一个问题,就是分配给Web浏览器的可用内存数量通常要比分配给桌面应用程序的少。这样做的目的主要是出于安全考虑,目的是防止运行javascript的网页耗尽全部系统内存而导致系统奔溃。内存限制问题不仅会影响给变量分配内存,同时还影响调用栈以及在一个线程中能同时执行的语句数量。

因此,确保占用最少的内存可以让页面获得更好的性能。而优化内存占用的最佳方式,就是为执行中的代码只保存必要的数据。一旦数据不再有用,最好通过将其值设置为null来释放其引用——即“解除引用”,该方法适用于大多数全局变量和全局对象的属性,而局部变量会在它们离开执行环境时自动被解除引用。例如:

function createPerson(name){var localPerson=new Object();localPerson.name=name;return localPerson;
}
var globalPerson=createPerson("Nicholas")
//手工解除globalPerson的引用
globalPerson = null;

注解:由于localPerson在createPerson()函数执行完毕后就离开了其执行环境,因此无需显示地为其解除引用。而全局变量globalPerson则需要我们在不使用它的时候手工为它解除引用。

不过,解除一个值的引用并不意味着自动回收该值所占用的内存。解除引用的真正作用是让值脱离执行环境,以便垃圾收集器下次运行时将其回收。

七、引用类型

ECMAScript中,引用类型是一种数据结构,用于将数据和功能组织在一起。引用数据类型有时候也被称为对象定义,因为它们描述的是一类对象所具有的属性和方法。

对象是某个特定引用类型的实例。新对象是使用new操作符后跟一个构造函数来创建的。构造函数本身就是一个函数,只不过该函数是出于创建新对象的目的而定义的。例如:

var person=new Object();

这行代码创建了Object引用类型的一个新实例,然后把该实例保存在了变量person中。使用的构造函数是Object,它只为新对象定义了默认的属性和方法。

1、Object类型

创建Object实例的方式有两种。第一种:
使用new 操作符后跟Object构造函数。

var person=new Object();

第二种:使用对象字面量表示法。对象字面量是对象定义的一种简写形式,目的在于简化创建包含大量属性的对象的过程。例如:

var person={name:"jack",age:20
}

注:使用对象字面量语法时,属性名也可以使用字符串。且通过该方法,实际上不会调用Object构造函数。通常也是开发人员青睐的方法。

一般而言,访问对象属性时使用的是点表示法。不过,在javascript中也可以使用方括号表示法来访问对象的属性。在使用方括号语法时,应该将要访问的属性以字符串的形式放在方括号中。如:

alert(person["name"]);\\方括号语法
alert(person.name);\\点表示法

建议使用点表示法。

2、Array类型

与其他语言不同的是,ECMAScript数组的每一项可以保存任何类型的数据。即,可以用数组的第一个位置来保存字符串,用第二位置来保存数值,用第三个位置来保存对象,以此类推。而且,ECMAScript数组的大小是可以动态调整的,即可以随着数据的添加自动增加以容纳新增数据。

创建数组有两种方式:
(1)、使用Array构造函数

var colors=new Array();//new可省略

(2)、数组字面量表示法
数组字面量由一对包含数组项的方括号表示,多个数组项之间以逗号隔开

var colors=["red","blue","green"];

注意:

使用数组字面量创建数组优于数组构造函数。

主要原因:[]与new Array() ,前者2个字符,后者11个字符。
此外,由于javascript的高度动态特性,无法阻止修改内置的Array构造函数,
即new Array()创建的不一定是数组。
因此,推荐使用数组字面量。

数组的length属性很有特点——它不是只读的。通过设置这个属性,可以从数组的末尾移除项或向数组中添加新项。

var colors=["red","blue","green"];
colors.length=2;
alert(colors[2]);//undefined

注:数组最多可以包含4294967295个项,这几乎已经能够满足任何编程需求了。如果想添加的项数超过这个上限值,就会发生异常 。

上面的结果也表面,javascript中数组是对象。如果访问不存在的对象,会返回undefined。访问不存在的数组索引,也会返回undefined。

2.1 检测数组

从ECMAScript3之后,出现了确定某个对象是不是数组的经典问题。对于一个网页或者全局作用域而言,使用instanceof操作符就能得到满意的结果:

if(value instanceof Array){//对数组执行某些操作
}

instanceof的问题在于:它假定只有一个全局执行环境。如果网页中包含对个框架,那实际上就存在两个以上不同的全局执行环境,从而存在两个以上不同版本的Array构造函数。

为了解决这个问题,ECMAScript5新增了Array.isArray()方法。这个方法的目的是最终确定某个值到底是不是数组,而不管它是在哪个全局执行环境中创建的。

if(Array.isArray(value)){//对数组执行某些操作
}

2.2 转换方法

所有对象都具有toLocaleString()、toString()和valueOf()方法。其中,调用数组的toString()方法会返回由数组中每个值的字符串形式拼接而成的一个以逗号分隔的字符串。而valueOf()返回的还是数组。
eg:

var colors=["red","blue","green"];
alert(colors.toString());//red,blue,green
alert(colors.valueOf());//red,blue,green
alert(colors);//red,blue,green

数组继承的toLocaleString()、toString()和valueOf()方法,在默认情况下都会以逗号分隔的字符串的形式返回数组项。而如果使用join()方法,则可以使用不同的分隔符来构建这个字符串。join()方法只接受一个参数,即用作分隔符的字符串,然后返回包含所有数组项的字符串。
eg:

var colors=["red","green","blue"];
alert(colors.join(","));//red,green,blue
alert(colors.join("||"));//red||green||blue

如果不给join()方法传入任何值,或者给它传入undefined,则使用逗号作为分隔符。IE7及更早版本会错误的使用字符串“undefined”作为分隔符。

注:如果数组中的某一项的值是null或者undefined,那么改值在join()、toLocaleString()和valueOf()方法返回的结果中以空字符串表示。

2.3栈方法

ECMAScript为数组专门提供了push()和pop()方法,以便实现类似栈的行为。

  • push()方法可以接收任意数量的参数,把它们逐个添加到数组末尾,并返回修改后数组的长度。
  • pop()方法则从数组末尾移除最后一项,减少数组的Length值,然后返回移除的项。

2.4队列方法

ECMASCript也提供了shift()和unshift()方法。

  • shift()能够移除数组中第一个项并返回该项,同时将数组长度减1。结合使用shift()和 push()方法,可以像使用队列一样使用数组。
  • unshift()与shift()用途相反,它能在数组前端添加任意个项并返回新数组的长度。因此,同时使用unshift()和pop()方法,可以从相反的方向来模拟队列,即在数组的前端添加项,从数组末端移除项。

2.5操作方法

  • concat() :
    会基于当前数组创建一个副本,然后将接收到的参数添加到这个副本末尾,最后返回新构建的数组。在没有给concat()方法传递参数的情况下,它只是复制当前数组并返回副本。
var colors=["red","green","blue"];
var colors2=colors.concat("yellow",["black","brown"]);
console.log(colors);//red,green,blue
console.log(colors2);//red,green,blue,yellow,black,brown
  • slice() :
    它能基于当前数组中的一个或多个项创建一个新数组;
    (1)接收一个参数时:slice()返回从该参数指定位置开始到当前数组末尾的所有项;
    (2)接收2个参数时,返回起始和结束位置之间的项,但不包括结束位置。
var colors=["red","green","blue","yellow","purple"];(1)var colors2=colors.slice(1);console.log(colors2);//green,blue,yellow,purple(2)var colors3=colors.slice(1,4);console.log(colors3);//green,blue,yellow
  • splice()—最强大的数组方法
   删除:指定2个参数---要删除的第一项的位置和要删除的项数;var colors=["red","green","blue"];var removed=colors.splice(0,1);console.log(colors);//green,blue插入:指定3个参数---起始位置、0(要删除的项数)、要插入的项,要插入的项可多个;inserts=colors.splice(1,0,"yellow","orange");console.log(colors);//green,yellow,orange,blue替换:指定3个参数---起始位置、要删除的项数、要插入的任意数量的项replaces=colors.splice(1,1,"red","purple");console.log(colors);//green,red,purple,orange,blue

2.6 位置方法

indexOf()和lastIndexOf()分别从数组开头向后与末尾开始向前查找;
两个方法,都返回要查找的项在数组中的位置,或者在没找到的情况下返回-1.
注意:在比较第一个参数与数组中每一项时,会使用全等操作符,即要求查找的项必须严格相等。

2.7 迭代方法

  • every()与some():用于查询数组中的项是否满足某个条件。
  • every():传入的函数必须对每一项都返回true,才返回true。
var numbers=[1,2,3,4,5,4,3,2,1];
var everyResult=numbers.every(function(item,index,array)){return (item>2);
});
alert(everyResult);//false

some():转入的函数对数组中的某一项返回true,就会返回true;

var someResult=numbers.some(function(item,index,array)){return (item>2);
});
alert(someResult);//true

filter():利用指定的函数确定是否在返回的数组中包含某一项

var numbers=[1,2,3,4,5,4,3,2,1];
var filterResult=numbers.filter(function(item,index,array)){return (item>2);
});
alert(someResult);//[3,4,5,4,3]

map():返回一个数组,这个数组的每一项都是在原始数组中的对应项上运行传入函数的结构;

var numbers=[1,2,3,4,5,4,3,2,1];
var mapResult=number.map(function(item,index,array)){return item*2;
});
alert(mapResult);//[2,4,6,8,10,8,6,4,2]

forEach():对数组中的每一项运行传入的函数,没有返回值;

2.8归并方法

reduce()和reduceRight(),迭代数组的所有项,然后构建一个最终返回的值。分别从数组第一项与最后一项开始,向前与向后遍历。

接收4个参数:前一个值、当前值、项的索引和数组对象,函数返回的任何值都会作为第一个参数自动传给下一项。

var values=[1,2,3,4,5];
var sum=values.reduce(function(prev,cur,index,array){return prev+cur;
});
alert(sum);//15

3、Function类型

ES中最有意思的莫过于函数,因为函数实际上是对象。
每个函数都是Function类型的实例,而且都与其他引用类型一样具有属性和方法。
函数是-对象,函数名是-指向函数对象的指针,不会与某个函数绑定。

(1)、3种函数定义方法:

  • 函数声明语法定义 (推荐)
 function sum(num1,num2){return num1+num2;}
  • 函数表达式定义
 var sum=function(num1,num2){return num1+num2;}
  • Function构造函数(不推荐)
    该方法不推荐使用,因为这种语法会导致解析两次代码,第一次是解析常规ES代码,第二次解析传入构造函数中的字符串,从而影响性能。
 var sum=new Function("num1","num2","return num1+num2");//Function构造函数可以接收任意数量的参数,但最后一个参数始终都被看成是函数体

(2)、没有重载
ES中没有函数重载的慨念。如果同时声明了两个同名函数,那么后一个会覆盖前一个。

(3)、函数声明与函数表达式
解析器在向执行环境中加载数据时,对函数声明和函数表达式并非一视同仁。
注意:

  • 解析器会率先读取函数声明,并使其在执行任何代码之前可用于访问;

  • 而函数表达式,必须等到解析器执行到它所在的代码行,才会真正被解析执行。

例如:
alert(sum(10,10));
var sum=function(num1,num2){return num1+num2;
}
//上述代码,在第一行就开始报错-"unexcepted identifier(意外标识符)错误",
并且不会执行到下一行。

(4)、作为值的函数
函数本身就是变量,所以,函数也可以作为值来使用。即,不仅可以像传递参数一样把一个函数传递给另一个函数,而且可以将一个函数作为另一个函数的结果返回。

function callSomeFunction(someFunction,someArgument){return someFunction(someArgument);
}

可以从一个函数中返回另一个函数,而且这也是极为有用的一种技术。

function createComparisonFunction(propertyName){return function(object1,object2){var value1=object1[propertyName];var value2=object2[propertyName];if(value1<value2){return -1;}else if(value1>valu2){return 1;}else {return 0;}};
}

(4)、函数内部属性
函数内部,有两个特殊的对象:arguments和this。其中,arguments是一个类数组对象,包含着传入函数中的所有参数。虽然,arguments的主要用途是保存函数参数,但这个对象还有一个名叫callee的属性。

callee属性,是一个指针。指向拥有这个arguments对象的函数。

function factorial(num){if(num<=1){return 1;}else {return num*arguments.callee(num-1)}
}
var trueFactorial=factorial;
factorial=function(){return 0;
};
alert(trueFactorial(5));//120
alert(factorial(5));//0

//变量trueFactorial获得了factorial的值,实际上是在另一个位置上保存了一个函数的指针。

函数内部的另一个特殊对象是this。其行为与java和C#中的this大致类似。this引用的是函数剧以执行的环境对象—或者也可以说是this值(当在网页的全局作用域中调用函数时this对象引用的就是window)。

window.color="red";
var o={color:"blue"};
function sayColor(){alert(this.color);
}
sayColor();//"red"
o.sayColor=sayColor();
o.sayColor();//"blue"

//sayColor()是在全局作用域中定义的,它引用this对象。由于在调用函数之前,this的值并不确定,因此this可能会在代码执行过程中引用不同的对象。当在全局作用域中调用sayColor()时,this引用的就是全局对象window。即,this.color会转换为window.color求值。而把这个函数赋给对象o并调用o.sayColor()时,this引用的是对象o,因此this.color会转换成对o.color求值。

函数名仅仅是一个包含指针的变量。即使是在不同的环境中执行,全局的sayColor()函数与o.sayColor()指向的仍然是同一个函数。

caller属性:这个属性保存着调用当前函数的函数的引用。如果在全局作用域中调用当前函数,它的值为Null。

function outer(){inner();}
function inner(){alert(inner.caller);}
//inner.caller指向outer(),等价于alert(arguments.callee.caller);outer();

注意,当函数在严格模式下执行,arguments.callee会导致错误。ES5还定义了arguments.caller属性,但在严格模式下访问它也会错误,在非严格模式下这个属性始终是undefined。
另外,严格模式下,不能为函数的caller属性赋值,否则会导致错误。

深拷贝与浅拷贝

基本数据类型(undefined,boolean,number,string,null)

存放在中,值不可变,可直接访问。即使是动态修改了基本数据类型的值,它的原始值也是不会改变。

如:

var str = "imaginecode";
console.log(strp[1]="f");
console.log(str);   //imaginecode

其次,基本类型的比较是值的比较,只要它们的值相等,就认为相等。

所以,通常为了避免值类型的转换,我们使用严格等 a===b进行比较。

引用类型(object)

存放在中,值可变。引用类型的变量,实际上是一个存放在栈内存的指针,指向堆内存中的地址。

如:

var a = [1,2,3];
a[1] = 6;
console.log(a); //[6,2,3]

其次,引用类型的比较是引用的比较。所以每次我们对 js 中的引用类型进行操作的时候,都是操作其对象的引用(保存在栈内存中的指针),所以比较两个引用类型,是看其的引用是否指向同一个对象。例如:

var a = [1,2,3];
var b = [1,2,3];
console.log(a===b); //false

虽然变量 a 和变量 b 都是表示一个内容为 1,2,3 的数组,但是其在内存中的位置不一样,也就是说变量 a 和变量 b 指向的不是同一个对象,所以他们是不相等的。

基于以上,由此可见:

基本类型的赋值是传值,而引用类型的赋值的传地址。

例如,也就是说引用类型的赋值是对象保存在栈中的地址的赋值,这样的话两个变量就指向同一个对象,因此两者之间操作互相有影响。

浅拷贝

先看一段代码:

var obj1 = {'name' : 'zhangsan','age' :  '18','language' : [1,[2,3],[4,5]],
};
var obj2 = obj1;
var obj3 = shallowCopy(obj1);
function shallowCopy(src) {var dst = {};for (var prop in src) {if (src.hasOwnProperty(prop)) {dst[prop] = src[prop];}}return dst;
}
obj2.name = "lisi";
obj3.age = "20";
obj2.language[1] = ["二","三"];
obj3.language[2] = ["四","五"];
console.log(obj1);
//obj1 = {//    'name' : 'lisi',
//    'age' :  '18',
//    'language' : [1,["二","三"],["四","五"]],
//};
console.log(obj2);
//obj2 = {//    'name' : 'lisi',
//    'age' :  '18',
//    'language' : [1,["二","三"],["四","五"]],
//};
console.log(obj3);
//obj3 = {//    'name' : 'zhangsan',
//    'age' :  '20',
//    'language' : [1,["二","三"],["四","五"]],
//};

先定义个一个原始的对象 obj1,然后使用赋值得到第二个对象 obj2,然后通过浅拷贝,将 obj1 里面的属性都赋值到 obj3 中。也就是说:

  • obj1:原始数据
  • obj2:赋值操作得到
  • obj3:浅拷贝得到

然后我们改变 obj2 的 name 属性和 obj3 的 age 属性,可以看到,改变赋值得到的对象 obj2 同时也会改变原始值 obj1。

而改变浅拷贝得到的的 obj3 则不会改变原始对象 obj1

这就可以说明赋值得到的对象 obj2 只是将指针改变,其引用的仍然是同一个对象,
而浅拷贝得到的的 obj3 则是重新创建了新对象。

然而,我们接下来来看一下改变引用类型会是什么情况呢,我又改变了赋值得到的对象 obj2 和浅拷贝得到的 obj3 中的 language 属性的第二个值和第三个值(language 是一个数组,也就是引用类型)。结果见输出,可以看出来,无论是修改赋值得到的对象 obj2 和浅拷贝得到的 obj3 都会改变原始数据。

因为浅拷贝只复制一层对象的属性,并不包括对象里面的为引用类型的数据。
所以就会出现改变浅拷贝得到的 obj3 中的引用类型时,会使原始数据得到改变。

由此,我们可以知道深拷贝与浅拷贝的一个区别:

  1. 深拷贝:将 B 对象拷贝到 A 对象中,包括 B 里面的子对象
  2. 浅拷贝:将 B 对象拷贝到 A 对象中,但不包括 B 里面的子对象

深拷贝:

深拷贝是对对象以及对象的所有子对象进行拷贝。

思路就是递归调用刚刚的浅拷贝,把所有属于对象的属性类型都遍历赋给另一个对象即可。

参考

  • javascript中的深拷贝和浅拷贝

【JavaScript 红宝书】JavaScript 高级教程第 3 版学习笔记相关推荐

  1. JavaScript 红宝书第4版上市啦!「文末送几本给大家」

    文末有活动 人的一生中总要读几本经典书,在这个"经典"泛滥的年代,什么才是权威的代表,我想大概是一本的书的口碑,能积累下上佳口碑的书,往往也是能经得住时间推敲的.比如这本: 我相信 ...

  2. 一起读JavaScript红宝书 —— 01JavaScript概述

    JavaScript高级程序设计读书笔记,前端神作不容错过.原书近100万字,内容太多,故作笔记尽量在不缩减内容的情况下缩减字数,方便复习和回顾. JavaScript概述 1. JavaScript ...

  3. 一起读JavaScript红宝书 —— 02语法基础(上)

    JavaScript高级程序设计读书笔记,前端神作不容错过.原书近100万字,内容太多,故作笔记尽量在不缩减内容的情况下缩减字数,方便复习和回顾. 语法基础(上) 1. 语法 2. 关键字和保留字 3 ...

  4. javascript 红宝书笔记之函数、变量、参数、作用域

    ECMAScript 不介意传进来多少个参数,也不介意传进来的参数类型. 理解参数: 命名的参数只提供便利,不是必需的. ECMAScript 的变量包含两种不同的数据类型的值.分别是 简单的数据构成 ...

  5. javascript 红宝书笔记之如何使用对象 如何操作数组

    对象定义  ===  引用类型,描述的是一类对象所具有的属性和方法 新对象的创建 方法 new + 构造函数       var person = new Object(); 对象字面量表示     ...

  6. JavaScript 红宝书学习记录

    函数 什么是函数? function,也叫 方法(method)或过程procedure.是一段 预定义 好的,并且可以被 反复执行的 代码块: 预定义:提前声明好,不是马上使用. 反复执行:可以被循 ...

  7. HTML5与CSS3基础教程第八版学习笔记16-21章

    第十六章,表单 HTML5引入了新的表单元素.输入类型和属性,以及内置的对必填字段.电子邮件地址.URL以及定制模式验证. 元素: <input type="email"&g ...

  8. HTML5与CSS3基础教程第八版学习笔记7~10章

    第七章,CSS构造块 CSS里有控制基本格式的属性(font-size,color),有控制布局的属性(position,float),还有决定访问者打印时在哪里换页的打印控制元素.CSS还有很多控制 ...

  9. javascript 高级程序设计_JavaScript 经典「红宝书」,几代前端人的入门选择

    人的一生中总要读几本经典书,在这个"经典"泛滥的年代,什么才是权威的代表,我想大概是一本的书的口碑,能积累下上佳口碑的书,往往也是能经得住时间推敲的.比如这本: 相信许多前端开发者 ...

  10. 红宝书初步研读随手笔记

    红宝书初步研读系列–第一二三章 红宝书第一遍研读,知识点整理–随手笔记 第一章 什么是JavaScriptS 1.1. JavaScript包括: ECMAScript (核心)DOM(文档对象模型) ...

最新文章

  1. jquery checkbox attr区别prop
  2. 《系统集成项目管理工程师》必背100个知识点-03项目管理过程组
  3. 阿里云物联网平台专题讲座
  4. AUTOSAR从入门到精通100讲(三十五)-AUTOSAR BswM三部曲-概念实践代码分析
  5. mysql+根密码是什么意思,重设MySQL根密码
  6. VMware ESXi下虚拟机的嗅探设置
  7. 跳转外部地址 带header_微信公众号如何加入超链接?个人订阅号实现点击跳转链接的方法!...
  8. 联想拯救者r7000p安装Linux双系统(一)
  9. gb和gib的区别_不同软硬件环境下NVMe SSD性能简测和一些SSD测试建议
  10. 【PyQt5】教你一招,用timer计时器做个打地鼠的小游戏
  11. 自动化软件测试工作内容,「自动化测试」自动化测试岗位职责(职位描述,任职要求)...
  12. 微信公众平台注册与认证图文教程分享
  13. SystemVerilog学习-10-验证量化和覆盖率
  14. dpkg dependency problems prevent configuration
  15. 教你用 Python 自制简单版《我的世界》
  16. 创始人退休后的Python,起飞还是没落?
  17. 我希望我早已知道的 103 条建议
  18. 《数据挖掘导论(完整版)》习题答案导航_补档
  19. 10个简洁的Python编码技巧
  20. Java 文件(夹)加密解密工具(附带压缩功能)

热门文章

  1. Linux基础知识点总结
  2. HarmonyOS移动应用学习笔记——2.HarmonyOS开发工具DevEco Studio安装
  3. 盘点电机重要应用的七大领域
  4. pip install numpy 安装numpy失败
  5. 全连接简单minist操作
  6. 微信小程序-视频教程-链接地址
  7. mysql 中文脱敏_怎样选择数据库脱敏系统?
  8. 使用原生js实现图片放大器效果
  9. bias tee电路设计-电容电感值
  10. 【视频直播篇七】Aliplayer的使用