本篇博客介绍一下JavaScript中对象和函数的相关概念。
Object
通过前面的学习,我们知道JavaScript中有六种数据类型:String、Number、Boolean、Null、Undefined和Object。其中前五种为基本数据类型,最后一种为引用数据类型。
对象属于一种复合的数据结构,在对象中可以保存多个不同的数据类型的属性。JS中对象分为以下三类:
- 内建对象。由ES标准中定义的对象。在任何的ES的实现中都可以使用,如:Math、String、Number、Function;
- 宿主对象。由JS的运行环境提供的对象,目前来讲主要指由浏览器提供的对象。如:BOM、DOM;
- 自定义对象。有开发人员自己创建的对象。
对象的创建
创建对象有两种方式:
- 使用new关键字创建。
- 使用对象字面量创建一个对象。
注意:对象字面量的属性名可以加引号,也可以不加,建议不加。如果要使用一些特殊的名字,则必须加引号。
向对象中添加属性
语法:对象.属性名 = 属性值;
读取对象中的属性
语法:对象.属性名;
- 如果读取对象中没有的属性,不会报错而是会返回undefined。
修改对象的属性值
语法:对象.属性名 = 新属性值;
删除对象的属性
语法:delete 对象.属性;
属性名和属性值
属性名:
- 对象的属性名不强制要求遵守标识符规范,什么乱七八糟的名字都可以,但是推荐遵循标识符规范;
- 如果要使用特殊的属性名,不能采用.操作符而应该采用[]操作符;
属性值:
- JS对象的属性值,可以是任意的数据类型。也可以是一个对象。
- 当然也可以是一个函数,因为JS中函数也是一个对象。如果一个函数作为一个对象保存,那么我们称这个函数是这个对象的方法。
in操作符
- 使用in操作符,可以检查一个对象中是否含有指定的属性。如果包含则返回true,否则返回false。
- 语法:
"属性名" in 对象;
枚举对象中的属性
- 我们可以使用for…in语句来枚举一个对象中的属性。
基本数据类型和引用数据类型
基本数据类型:
- String、Number、Null、Boolean和Undefined都是基本数据类型;
- 基本数据类型的值直接在栈中存储,值与值之间是独立存在,修改一个变量不会影响其他变量;
- 基本数据类型之间使用==进行比较,比较的是两个变量的值。
引用数据类型:
- Object是引用数据类型;
- 引用数据类型是保存在堆内存中的,每创建一个对象,就会在堆内存中开辟出一个新的空间,而变量保存的是对象的地址;
- 引用数据类型之间使用==进行比较,比较的是对象的内存地址。
Function
- 函数也是一个对象。函数中可以封装一些功能(代码),在需要时可以执行这些功能(代码)。
- 函数中可以保存一些代码在需要的时候调用,使用
typeof 函数;
返回的是function。
创建一个函数
创建函数有三种方法:
- 方法一:使用构造方法创建一个函数。将代码以字符串的形式传入构造方法。
- 方法二:使用函数声明创建一个函数。
- 方法三:使用函数表达式创建一个函数。
参数和返回值
函数的参数:
- 调用函数时,解析器不会检查实参的类型,所以要注意,是否有可能会接收到非法的参数。如果有可能接收到非法参数,需要对参数类型进行检查;
- 解析器也不会检查实参的数量,多余的实参不会被赋值。
- 如果实参的数量少于形参,没有对应实参的形参值为undefined;
- 函数的实参可以是任意类型。
- 定义形参相当于在函数作用域中声明了变量。
函数的返回值:
- 函数内部可以声明一个函数。
- 函数返回值可以是任意的数据类型,也可以是一个对象,当然也可以是一个函数。
立即执行函数
- 我们知道函数定义完并不会立即执行,而是在函数被调用时才会执行。
- 立即执行函数在函数定义完,立即就会被调用。
- 语法:
(function(){})();
- 立即执行函数只能执行一次。
call和apply方法
- 这两个方法都是函数对象的方法,需要通过函数对象来调用;
- 当对函数对象调用call()和apply()都会调用函数执行;
- 在调用call和apply的时候,可以将一个对象指定为第一个参数,此时这个对象将会成为函数执行时的this。
- call方法可以将实参在对象之后依次传递,apply方法需要将实参封装到一个数组中统一传递。
arguments
每次调用函数时,浏览器每次都会传递进两个隐含的参数:
- 函数的上下文对象this;
- 封装实参的对象arguments。
arguments是一个类数组对象。不是一个数组对象。
我们在调用函数时,我们所传递的实参都会在arguments中保存,我们可以通过arguments.length
来获取实参的数量;也可以通过下标来使用传递来的实参,即使我们不定义形参。
arguments有一个属性callee,这个属性对应一个函数对象,就是当前正在执行的函数对象。
作用域
JS中有两种作用域:
- 全局作用域;
- 函数作用域。
全局作用域
- 直接编写在script标签中的JS代码,都在全局作用域中;
- 全局作用域在页面打开的创建,页面关闭时销毁;
- 全局作用域中有一个全局对象window,我们可以直接使用。它代表的是一个浏览器窗口,由浏览器窗口创建,在全局作用域中,创建的变量都做为window对象的属性保存;创建的函数都会作为window的方法保存。
- 全局作用域中的变量都是全局变量,在页面的任意部分都可以访问到。
变量的声明提前:
- 变量未创建使用会报错,属性不存在使用不会报错,返回undefined。
- 定义一个变量可以省略var关键字。
- 使用var关键字声明的变量会在所有代码执行之前被声明;如果不使用var关键字,则变量不会被声明提前。
函数的声明提前:
- 使用函数声明形式创建的函数:
function 函数名(){}
,会在所有的代码执行之前就被创建,所以可以在函数声明前调用; - 使用函数表达式创建的函数,不会被声明提前,不会被提前创建。
函数作用域
- 调用函数时创建函数作用域,函数执行完毕以后,函数作用域销毁;
- 每调用一次函数就会创建一个新的函数作用域,它们之间相互独立;
- 在函数作用域中可以访问到全局作用域变量;
- 全局作用域中无法访问到函数作用域中的变量。
- 当在函数作用域中操作一个变量时,它会先在自身作用域中寻找,如果有就直接使用,如果没有则向上一级作用域寻找,直到全局作用域。如果全局作用域也没有找到,则报错;
- 如果想要在函数作用域中访问全局作用域中的变量,可以使用
window.属性
; - 函数作用域中也有声明提前特性。使用var关键字声明的变量,会在函数中所有的代码执行之前被声明,函数声明创建的函数也会在所有代码执行之前创建;
- 函数作用域中不使用var关键字声明的变量都会成为全局变量。
this
- 解析器在调用函数时,会向函数内部传递一个隐含的参数,这个隐含的参数就是this;
- this指向的是一个对象,这个对象我们称为函数执行的上下文对象;
根据函数调用方式的不同,this会指向不同的对象。
- 当我们以函数的形式调用时,this永远都是window。
- 以方法的形式调用时,this就是调用方法的那个对象。
- 以构造函数的形式调用的时候,this指向新创建的对象。
使用工厂方法创建对象
我们可以通过工厂方法来大批量的创建对象。
下面,我们来看一个例子来具体理解一下如何使用?
构造函数
- 构造函数就是一个普通的函数,创建方式和普通函数没有区别;
- 构造函数习惯上首字母大写;
- 构造函数和普通函数的区别就是调用的方式不同,普通函数直接调用,而构造函数需要使用new关键字来调用。
下面我们来看一个具体的例子:
构造函数执行流程:
- 立刻创建一个新的对象;
- 将新的对象设置为函数的this,构造函数中的this指向新对象;
- 执行函数中的代码;
- 将新创建的对象作为返回值返回。
使用同一个构造函数构造出来的对象,我们称为一类对象。我们将通过一个构造函数创建的对象,称为是类的实例。
- 使用instanceof可以检查一个对象是不是一个类的实例。语法:
对象 instanceof 构造函数;
。 - 所有对象都是Object的后代,任何对象 instanceof Object结果都是true。
原型对象
- 我们所创建的每一个函数,解析器都会向函数中添加一个属性prototype;这个属性对应着一个对象,这个对象就是我们所说的原型对象。
我们来具体画一下示意图: - 如果函数作为普通函数调用,prototype没有任何作用;当函数以构造函数的方式调用时,它所创建的对象中都会有一个__proto__属性指向该构造函数的原型对象;
- 我们知道Student构造函数中有prototype属性指向其原型对象,同样的使用Student构造出的对象中也有__proto__属性,该属性同样指向Student的原型对象。原型对象相当于一个公共的区域,所有同一个类的实例都可以访问到这个原型对象。
- 当我们访问对象的一个属性或方法时,它会先在对象自身中寻找,如果有则直接使用,如果没有则会到原型对象中寻找,注意原型对象也是一个对象,也有其原型对象,所以会一直去原型对象中找,直到Object对象的原型,Object对象的原型没有原型,如果在Object的原型中依然没有找到,则返回undefined。
原型对象有什么用呢?
首先,我们再来看一下前面的通过构造函数创建对象的代码:
使用这种方式来创建对象是有问题的,Student函数中为每一个对象都添加了info方法。方法是在构造函数内部创建的。构造函数每执行一次,就会创建一个info方法。而这些方法是一模一样的,这是完全没有必要的,我们完全可以让所有的对象共享同一个方法。
如何解决这个问题呢?我们有两种方法:
- 将info方法写到全局作用域中;
这种方法虽然可以避免创建多个函数对象,但是将函数定义在全局作用域中会污染全局作用域的命名空间。而且定义在全局作用域中很不安全。 - 使用原型对象,将函数写到原型对象中。
总结:
- 以后创建构造函数时,可以将这些对象共有的属性和方法,同一添加到构造函数的原型对象中,这样不用分别为每一个对象添加,也不会影响到全局作用域,就可以使每个对象都具有这些属性和方法了。
in和hasOwnProperty
- 我们可以使用in来检查对象中是否具有某个属性,如过对象中没有,但是原型对象中有,也会返回true;
- 我们可以使用对象的hasOwnProperty()方法来检查对象自身中是否包含指定属性,使用该方法只有对象本身包含时才返回true,不包括原型对象中有。
toString
- 当我们在页面中打印一个对象时,实际上输出的是对象的toString方法的返回值。
- 如果我们希望在输出对象时不输出[object Object],可以为对象添加一个toString()方法。