对象分类
内建对象
由ES标准中定义的对象,在任何的ES的实现中都可以使用。比如Math、Date、基本数据类型对象等
宿主对象
由JS的运行环境提供的对象,目前来讲主要指由浏览器提供的对象。比如:BOM、DOM
自定义对象
有开发人员自己创建的对象。
对象的基本操作
创建对象
使用new关键字调用的函数是构造函数,构造函数是专门用来创建对象的函数。
var obj = new Object();
console.log(obj)
使用typeof 可以检查一个变量是否是对象类型。
向对象添加属性:对象.属性名 = 属性值
obj.name = "孙悟空";
读取对象中属性值:对象.属性名。如果读取的属性对象中没有,不会报错而是返回undefined
修改对象的属性值:对象.属性名 = 新属性值
删除对象的属性:delete
对象.属性名
delete obj.name; //这样就删除了对象的name属性
属性名和属性值
属性名命名规范:不强制要求遵循标识符的规范,想用什么就用什么(如果要使用特殊的属性名,不能采用.
的方式来操作,需要采用对象["属性名"] = 属性值
方式操作,读的时候也需要对象["属性名"]
方式读取),但是在实际开发中还是遵循标识符的规范。
一般使用[]这种方式操作属性更加灵活,因为在[]中可以直接传递一个变量。用.
不可以直接传递变量
var obj = new Object();
obj.name = "小明";
obj["789"] = "111";
console.log(obj);
var num = "789";
console.log(obj[num]);
JS属性值可以是任意的数据类型。使用in
运算符可以检查一个对象中是否含有指定的属性。返回值为true/false
;
console.log("name" in obj); //true
console.log("789" in obj); //true
console.log("test" in obj); //false
基本数据类型和引用数据类型
基本数据类型
String Number Boolean Null Undefined
引用数据类型
Object array function
字面量
在创建对象时,也会经常使用这样方式{}
大括号方式创建对象。
var obj = {属性名1:"属性值1", 属性名2:"属性值2", ... , 属性名n:"属性值n"};
这两种创建对象的方式都是一样的,属性名可以加引号也可以不加,建议加引号。如果一个属性之后没有属性了,就不要写逗号了。
方法
对象的属性值可以是任何数据类型,也可以是函数,这样的函数称之为这个对象的方法。
//定义一个方法2
obj.sayName = function(){
console.log(obj.name);
}
//定义一个方法2
obj1 = {
name:"小明",
sayName:function(){
console.log(obj.name);
}
}
//调用方法
obj.sayName();
obj1.sayName();
枚举对象中的属性
使用for ... in
, 语法:
for(var n in 对象){
//...
}
for...in
语句对象中有几个属性,循环体就会执行几次。每次执行时,会将对象中的一个属性的名字赋值给变量。取值时可以使用obj[n]。
作用域
作用域指一个变量的作用的范围。在JS中一共有两种作用域:
全局作用域
直接编写在script标签中的JS代码, 并未写在函数、对象中的变量都在全局作用域。
全局作用域在页面打开时创建,在页面关闭时销毁。
在全局作用域中有一个全局对象window, 它代表的是一个浏览器的窗口,它由浏览器创建我们可以直接使用。在全局作用域中:
- 创建的变量都会作为window对象的属性保存。
- 创建的函数都会作为window对象的方法保存。
全局作用域中的变量都是全局变量,在当前页面的任意的部分都可以访问的到。
函数作用域
调用函数时创建函数作用域,函数执行完毕以后,函数作用域销毁。
每调用一次函数就会创建一个新的函数作用域,他们之间是互相独立的。
在函数作用域中可以访问到全局作用域的变量,在全局作用域中无法访问到函数作用域的变量。变量访问的就近原则:当在函数作用域操作一个变量时,它会先在自身作用域中寻找,如果有就直接使用。如果没有则向上一级作用域中寻找,直到找到全局作用域。如果全局作用域中依然没有找到,则会报错
ReferenceError
。在函数中要访问全局变量可以使用window
对象。var a = 10; function fun(){ var a ="我是fun函数中的变量a"; var b = 20; // console.log( "a = "+a); function fun2(){ console.1og( "a = "+window.a); //使用全局中的a } fun2(); } fun();
声明提前
变量的声明提前
console.log("a ="+a); //a undefined
var a = 123;
function fun1(){ //函数中变量也会声明提前到函数顶行
console.log("b ="+b); //b undefined
var b = 456;
};
fun1();
上面代码不会报错,只会警告a是undefined。
变量的声明提前:使用var关键字声明的变量,会在所有的代码执行之前被声明(不会被赋值),但是如果声明变量时不使用var关键字,则变量不会被声明提前。
函数的声明提前
使用函数声明形式创建的函数function 函数 (){ ... }
, 它会在所有的代码执行之前最先被创建。所以我们可以在函数声明前来调用函数。
使用函数表达式创建的函数,不会被声明提前,所以不能在声明前调用。
fun1(); //正常调用, 因为fun1()是函数声明,转化为window.fun1。window为全局变量,所以可以调用。
fun2(); //报错:fun2变量使用了提前变量声明,但是没有被赋值为一个函数类型,调用fun2()报错。
//函数声明
function fun( ){
console.log("我是一个fun函数");
};
//函数表达式
var fun2 = function(){
console.log("我是fun2函数");
};
function fun1(){ //函数中函数也会声明提前到函数顶行
fun3();
function fun3(){
console.log("正常不正常?");
}
};
fun1();
函数在执行过程中都会先看自己作用域中是否有var声明的变量,如果有,检查是否执行变量提前声明操作。如果没有,使用就近原则找上层。
this
解析器在调用函数时每次都会向函数内部传递进一个隐含的参数,这个隐含的参数就是this。
这个对象我们称为函数执行的上下文对象,根据函数的调用方式的不同,this会指向不同的对象(谁调用方法,this就指向谁)。
function fun(){
console.log(this);
}
var obj = {
name:"孙悟空",
sayName:fun
};
fun() //这里相当于是window.fun() 所以打印是window对象
obj.sayName() //obj对象
- 以函数的形式调用时,this永远都是window
- 以方法的形式调用时,this就是调用方法的那个对象
- 以构造函数的形式调用时,this就是新创建的那个对象
function func(){
console.log(this.name)
};
var obj1 = {
name:"悟空",
sayName:func
};
var obj2 = {
name:"八戒",
sayName:func
};
obj1.sayName();
obj2.sayName();
使用工厂方法创建对象
function createPerson(name, age, gender){
var obj = {
name:name,
age:age,
gender:gender,
sayName:function(){
console.log(this.name);
}
}
return obj;
}
var xmf = createPerson("小明他爸", "45", "男");
var xmm = createPerson("小明他妈", "40", "女");
var xm = createPerson("小明", "10", "男");
xmf.sayName();
xmm.sayName();
xm.sayName();
构造函数
使用工厂方法创建的对象使用的构造函数都是Object(),所以创建的对象都是Object这个类型,就导致我们无法区分出多种不同类型的对象。
创建一个构造函数,专门用来创建Person对象的。构造函数就是一个普通的函数,创建方式和普通函数没有区别,不同的是构造函数习惯上首字母大写。
构造函数和普通函数的区别就是调用方式的不同,普通函数是直接调用,而构造函数需要使用new
关键字来调用.
function Person(name, age, gender){
this.name = name;
this.age = age;
this.gender = gender;
this.sayName = function(){
console.log(this.name);
}
return this;
}
var xm = new Person("小明", "10", "男");
xm.sayName();
console.log(xm);
构造函数的执行流程:
- 立刻创建一个新的对象
- 将新建的对象设置为函数中this,在构造函数中可以使用this来引用新建的对象
- 逐行执行函数中的代码
- 将新建的对象作为返回值返回
使用同一个构造函数创建的对象,我们称为一类对象,也将一个构造函数称为一个类。我们将通过一个构造函数创建的对象,称为是该类的实例。
使用instanceof
可以检查一个对象是否是一个类的实例语法:
对象 instanceof 构造函数
如果是:则返回true, 否则返回false
所有的对象都是Object的后代,所以任何对象和Object使用instanceof
检查时都会返回true。
修改构造函数
创建一个Person构造函数,在Person构造函数中,为每一个对象都添加了一个sayName方法,目前我们的方法是在构造函数内部创建的,也就是构造函数每执行一次就会创建一个新的sayName方法。也就是说所有实例的sayName都是唯一的。那么如果构造方法执行10000次就会创建10000个新的方法,而10000个方法都是一样的。
解决方法:构造函数中的方法提取到全局作用域中。但是这样将会污染了全局作用域的命名空间(不同同时存在两个叫sayName名字的方法),并且不安全(多人开发中,同名的函数下面的方法会覆盖上面的方法)。所以就用到了下面的原型。
原型(prototype)
首先明白一点:函数也是对象。
我们所创建的每一个函数,解析器都会向函数中添加一个属性prototype。这个属性对应着一个对象,这个对象就是我们所谓的原型对象。
如果函数作为普通函数调用prototype没有任何作用。当函数以构造函数的形式调用时(var n = new Person()
),它所创建的对象中都会有一个隐含的属性,指向该构造函数的原型对象,我们可以通过__proto__
来访问该属性。
原型对象就相当于一个公共的区域,所有同一个类的实例都可以访问到这个原型对象,我们可以将对象中共有的内容,统一设置到原型对象中。
function Person(){
}
var p1 = new Person();
p1.name = "p1";
console.log(Person.prototype); //原型对象
console.log(Person.prototype == p1.__proto__); //true 这两个属性指向的对象是一样的, 都是原型对象
var p2 = new Person();
p2.name = "p2";
console.log(Person.prototype == p2.__proto__); //true 这两个属性指向的对象是一样的, 都是原型对象
Person.prototype.a = "原型对象中的属性a";
p1.a = "p1对象中的a"
console.log(p1.a); //p1对象中的a,这里会先到p1对象中找a属性,然后到原型中找a属性。
console.log(p2.a); //原型对象中的属性a
p2.__proto__.sayName = function(){
console.log(this.name); //this指的是当前对象的名称
}
p1.sayName(); //p1
p2.sayName(); //p2
当我们访问对象的一个属性或方法时,它会先在对象自身中寻找,如果有则直接使用,如果没有则会去原型对象中寻找,如果找到则直接使用。
以后我们创建构造函数时,可以将这些对象共有的属性和方法,统一添加到构造函数的原型对象中,这样不用分别为每一个对象添加,也不会影响到全局作用域,就可以使每个对象都具有这些属性和方法了。
使用in
检查对象中是否含有某个属性时,如果对象中没有但是原型中有,也会返回true。可以使用对象的hasOwnProperty()来检查对象自身中是否含有该属性,返回值也是bool类型。
hasOwnProperty方法的来源
原型对象也是对象,所以它也有原型。当我们使用一个对象的属性或方法时,会现在自身中寻找,自身中如果有,则直接使用,如果没有则去原型对象中寻找,如果原型对象中有,则使用,如果没有则去原型的原型中寻找,直到找到Object对象的原型。Object对象的原型没有原型,如果在Object的原型中依然没有找到,则返回undefined.
Object对象的原型没有原型,所以Object.__proto____proto__ = null
。
toString
当我们直接在页面中打印一个对象时,事件上是输出的对象的toString()方法的返回值。如果我们希望在输出对象时不输出[object Object],可以为对象添加一个toString()方法。
function Person(name, age){
this.age = age;
this.name = name;
}
var p1 = new Person("悟空", 15);
console.log(p1); //[object Object]
var res = p1.toString();
console.log(res);//[object Object]
console.log(p1.hasOwnProperty("toString")); //false
console.log(p1.__proto__.hasOwnProperty("toString")); //false
console.log(p1.__proto__.__proto__.hasOwnProperty("toString")); //false
Person.prototype.toString = function(){ //重写Object对象的__proto__的toString方法
return "name = " + this.name + ", age="+this.age;
}
console.log(p1.toString());
垃圾回收(GC)
当一个对象没有任何的变量或属性对它进行引用,此时我们将永远无法操作该对象,此时这种对象就是一个垃圾,这种对象过多会占用大量的内存空间,导致程序运行变慢,所以这种垃圾必须进行清理。
在JS中拥有自动的垃圾回收机制,会自动将这些垃圾对象从内存中销毁,我们不需要也不能进行垃圾回收的操作。我们需要做的只是要将不再使用的对象设置null即可。