不是5种基本数据类型,就都是对象
4.1 对象的分类
-
内建对象
由ES标准定义的对象,在任何ES的实现中都可以使用
比如Math,String,Number,Funtion……
-
宿主对象
由JS的运行环境提供的对象,目前来讲主要指浏览器提供的对象
比如DOM,BOM
-
自定义对象
由开发人员自己创建的对象
4.1.1对象基本操作
-
创建对象:
使用new关键字调用的函数,是构造函数constructor
构造函数是专门用来创建对象的函数
使用typeof检查一个对象时,会返回object
var obj = new Object();在对象中保存的值称为属性
-
向对象中添加属性
语法:对象.属性名 = 属性值;
obj.name = "Tom"; -
特殊属性名
属性名不强制要求遵守标识符规范,但是在使用中尽量按照标识符规范写
如果采用特殊的属性名,不能采用”.”的方式来操作
语法:对象[“属性名”] = 属性名
obj["123"] = 789; console.log(obj["123"]); let n = "123"; console.log(obj[n]); //输出789属性值可以是任意数据类型,可以是基本数据类型,可以是个对象,即套娃
-
读取对象中的属性
语法:对象.属性;
如果读取对象中没有的属性,不会报错而是会返回undefined
-
改对象中的属性
语法:对象.属性 = 新值;
-
删除对象中的属性
语法:delete 对象.属性;
delete obj.name;
4.2 遍历
4.2.1 In 运算符
检查一个对象中是否含有该属性
语法: “属性名” in 对象
console.log("name" in obj);
// 有返回true,没有就返回false
JS中的变量都是保存到栈内存中的
基本数据类型的值直接在栈内存中存储
值与值之间是独立存在的,修改一个变量不会影响到其他的变量
对象时保存到堆内存中的,每创建一个新的对象,就会在堆内存中开辟一个新的空间
跟指针差不多
而变量保存的是对象的地址(对象的引用),如果两个变量保存的是同一个对象引用
当其中一个通过一个变量修改属性时,另一个也会受到影响
当两个基本数据类型比较时,就是比较值
当两个引用数据类型比较时,它是比较对象的内存地址
如果两个对象内容一模一样,但是地址不同,还是返回false
4.2.2 使用字面量创建对象
使用字面量创建对象时,可以直接指定对象中的属性
语法: var 对象名 = {属性名:属性值,属性名:属性值……}
属性名可以加引号,一般不加,特殊的名字必须加引号
let obj = {
name:"孙悟空",
gender:"男"
};
4.3 函数 Function
4.3.1函数基本操作
-
创建函数
使用函数声明创建函数
语法:function 函数名([形参1,形参2……]){
//函数体
}
// 函数声明创建函数
function first(num1,num2){
// 函数体
}
// 函数表达式创建函数
var fun = function(num1,num2){
// 函数体
};
可以使用构造函数创建函数,但是一般不用
-
函数参数传递
调用函数时,解析器不会检查实参的类型和数量
函数的实参可以是任意数据类型。
function first(num1,num2){ console.log(num1 + num2); } first(123,"hello"); //123hello first(true,false); //1 first(123,"hello",456); //123hello first(23); //NaN如果实参的数量多于形参数量,多于的实参将不会被赋值
如果实参的数量少于形参数量,没有对应实参的形参将是undefinde
可以将一个匿名函数当做实参传输给另一个函数
当传输的参数过多时,可以将参数打包成一个对象
// 创建对象 var obj = { name:"孙悟空", gender:"男", age:18, address:"花果山" }; // 函数声明创建函数 function first(o){ console.log("我叫" + o.name + ",我是个" + o.gender +"人,我今年" + o.age + "岁了,我来自" + o.address); } first(obj); // 我叫孙悟空,我是个男,我今年18岁了,我来自花果山 -
函数返回值
函数中使用:可以跟任意类型的值
return 值
不跟任何值和不写return 返回undefined
-
立即执行函数
(function(a,b){ console.log("a=" +a); console.log("b=" +b); })(12,34)
方法
函数也可以称为对象的属性,
如果一个函数作为一个对象的属性保存
那么我们称这个函数是这个对象的方法
调用这个函数就说调用对象的方法(method)
它只是名称上的区别,没有任何其他的区别。
4.3.2 枚举对象中的属性
-
For…in语句
语法:
For(var 变量名 in 对象名){
//代码块
}
For…in语句,对象中有几个属性就执行几次
每次执行时,会将对象中一个属性的名字赋值给变量
for(var n in obj){ console.log("属性名:" + n); console.log(n +":" + obj[n]); }用中括号传一个变量就可以取对象里面的值
4.3.3 作用域
-
全局作用域
直接编写在script标签中的JS代码,都在全局作用域
全局作用域在页面打开时创建,在页面关闭时销毁
在全局作用域中有一个全局对象window,它代表的是一个浏览器的窗口
它由浏览器创建,我们可以直接使用它
在全局作用域中:
创建的变量都会作为window对象的属性保存
创建的函数都会作为window对象的方法保存
-
变量的声明提前:变量提升
使用var关键字声明的变量,会在所有代码执行之前被声明(但是不会赋值,执行到变量的时候才赋值)
如果变量声明不使用var关键字,则变量不会被声明提前
在函数作用域中也会有变量声明提前
-
函数的声明提前(会有这样的面试题)
使用函数声明创建函数 function 函数名(){} 会在所有代码执行之前声明
这种函数写到哪都行
使用函数表达式创建函数 var 变量 = function(){} 只有变量提前声明,但是没有函数声明提前
-
函数作用域(局部作用域)
调用函数时创建函数作用域,函数执行完毕以后,函数作用域销毁
每调用一次函数就会创建一个新的函数作用域,他们之间是相互独立的
在函数作用域中可以访问全局变量
全局作用域无法访问函数作用域的变量
当在函数作用域操作一个变量时,它会现在自身作用域中寻找,如果有就直接使用
如果没有则向上一级作用域中寻找,上一级未必是全局作用域
全局作用域中也没有就会报错
在函数作用域中使用全局作用域的变量,就需要使用window对象
var a = "全局作用域的a"; function fun() { var a = "fun的a"; function fun2() { console.log("a=" +a); //a=fun的a console.log("a=" + window.a); //a=全局作用域的a } fun2(); } fun();在函数作用域中也会有变量声明提前
同样的,函数声明也会提前
相当于小的全局作用域,就是生命周期比较短
function test() { //函数作用域中也存在变量声明提前 console.log("a=" + a); //a=undefined var a = 35; } // 相当于 function test() { var a; console.log("a=" + a); //a=undefined a = 35; }在函数中,不使用var声明的变量都会成为全局变量
function test() { //函数中不加var的变量就会被认为是全局变量 d = 35; // 相当于 window.d = 35; }
4.3.4 this关键字
解析器在调用函数每次都会向函数内部传递一个隐含的参数,(还有一个隐含的参数,arguments)
这个隐含的参数就是this,this指向的是一个对象,
这个对象我们称为函数执行的上下文对象
根据函数调用方式的不同,this会指向不同的对象
-
以函数的形式调用时,this永远都是window
-
以对象的形式调用时,this就是调用方法的那个对象
var obj = { name:"孙悟空", sayName:fun } function fun() { console.log(this); } // 以函数形式调用 fun(); //返回window // 以对象形式调用 obj.sayName(); //返回obj使用工厂方法创建对象
将创建对象的重复语句封装到函数中
创建的对象都是Object类型,有时候无法区分
function createPerson(name, age, gender) { var obj = new Object(); obj.name = name; obj.age = age; obj.gender = gender; console.log(obj); return obj; } var obj2 = createPerson("孙悟空",24,"男"); var obj3 = createPerson("蜘蛛精",18,"女"); var obj4 = createPerson("白骨精",16,"女");
(1)构造函数
构造函数就是一个普通的函数,创建方式和普通的函数没有区别
不同的是构造函数习惯上首字母大写
构造函数和普通函数的区别就是调用方式的不同
普通函数是直接调用,而构造函数需要使用new关键字来调用
(2)构造函数执行过程:
- 立刻创建一个新的对象
- 将新建的对象设置为函数中的this,在构造函数中可以使用this来引用新建的对象
- 逐行执行函数中的代码
- 将新建的对象做饭返回值返回
使用同一个构造函数创建的对象,我们称为一类对象,也将一个构造函数称为一个类
我们将通过一个构造函数创建的对象,称为是该类的实例
所以per2,per3,per4都是Person类的实例
(3)This的4种情况
-
当以函数的形式调用时,this是window
-
当以方法的形式调用时,谁调用方法this就是谁
-
当以构造函数的形式调用时,this就是新创建的那个对象
-
使用call()和apply()调用时,this是指定的那个对象
function Person(name, age, gender) { this.name = name; this.age = age; this.gender = gender; this.sayName = function () { alert(this.name); } console.log(this); } var per2 = new Person("孙悟空",24,"男"); var per3 = new Person("蜘蛛精",18,"女"); var per4 = new Person("白骨精",16,"女");运行结果:
上面构造函数中的sayName方法还是有缺陷的,详情见4.3.5
使用instanceof 可以检查一个对象是否是一个类的实例
语法:
对象 instanceof 构造函数
如果是,则返回true,否则返回false
```
console.log(per2 instanceof Person);
```
> 所有对象都是Object的后代
>
> 所以任何对象和Object做insranceof检查时都会返回true
(4)使用call(),apply()调用函数
他们两个都可以指定this指向,区别就是传递实参的形式不同
var obj = {name:"Tom"};
var obj2 = {name:"Jack"};
function fun(a,b) {
console.log("a=" +a); //2
console.log("b=" +b); //3
console.log(this.name);
}
fun.call(obj,2,3); //obj
fun.apply(obj2,[2,3]); //obj2
(5)封装实参的对象arguments
它也是在调用函数时,浏览器传递的隐含参数,和this一样
是一个类数组对象,也可以通过索引操作数据,也可以获取长度 (arguments.length就是实参的个数)
在调用函数时,我们所传递的实参都会在arguments中保存
我们即使不定义形参,也可以通过arguments调用实参
arguments[0] 表示第一个实参,以此类推
它里面有个属性叫callee,
这个属性对应一个函数对象,就是当前正在指向的函数的对象。
4.3.5 原型prototype
上面构造函数中的sayName方法还是有缺陷的
- 每创建一个对象就会在内部执行创建方法,将sayName写在外部的话,会污染全局作用域的命名空间
原型上的方法是给实例化对象用的,
实例对象的隐式原型 = 构造函数的显式原型
-
每个函数function都有一个prototype,即显式原型(属性)
他的默认值是一个空的Object对象,(但是Object实例对象不满足)
-
当函数以构造函数的形式调用时,它所创建的对象中都会有一个隐含的属性,也叫隐式原型(属性)
指向该构造函数的原型对象,我们可以通过 __proto__来访问该属性
函数也是通过Funtion构造函数实现的对象,所以函数的隐式原型__proto__属性都一样
-
对象的隐式原型的值为其对应构造函数的显式原型的值,原型是一个地址值
原型对象就相当于一个公共的区域,所有同一个类的实例都可以访问到这个原型对象
我们可以将对象中的共有的内容,统一设置到原型对象中。
原型链:
当我们访问对象的一个属性或者方法是,它会现在对象自身中寻找,如果有则直接使用,
如果没有,则会去原型对象中寻找,找到再使用
实例的原型对象永远指向自己缔造者的原型对象
//向原型中添加属性
Person.prototype.a = 123;
console.log(per2.a); //123
我们创建构造函数时,可以将这些对象共有的属性和方法,统一添加到构造函数的原型中
这样不用分别为每一个对象添加,也不会影响到全局作用域,就可以是每个对象都具有这些属性和方法了
原型和对象示意图:
完善后的创建对象代码:
function Person(name, age, gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
var per2 = new Person("孙悟空",24,"男");
var per3 = new Person("蜘蛛精",18,"女");
var per4 = new Person("白骨精",16,"女");
//向原型中添加sayName方法
// 这样即保证这个函数只有一个,也不会影响到全局作用域
Person.prototype.sayName = function(){
alert(this.name);
};
console.log(per2.sayName());
console.log(per2.__proto__ == Person.prototype); //true
原型对象也是对象,所以它也有原型
当我们使用一个对象的属性或者方法时,会先在自身中查找
自身如果有,直接使用
如果自身中没有就会去原型总查找,有就使用
如果没有就去原型的原型中查找,直到Object对象的原型
Object对象的原型没有原型,如果在Object中依然没有找到,则返回undefined
-
使用in检查对象中是否含有某个属性时,如果对象中没有但是原型中有,也会返回true
-
可以使用对象的hasOwnProperty()来检查对象自身中是否含有该属性
使用该方法只有当对象自身中含有属性时,才会返回true
console.log(per2.hasOwnProperty("name")); //true
这个方法存放在原型对象的原型中
就是Object对象的原型
console.log(Person.__proto__.__proto__.hasOwnProperty);//显示对象
console.log(Person.__proto__.__proto__.__proto__); //null
4.3.6 toString方法
当我们直接在页面中打印一个对象时,事件上是输出对象的toString()方法的返回值
这个方法在Object对象的原型中
现在浏览器调试工具中都自带了
4.3.7 垃圾回收(GC)
软件或者系统运行过程中会产生垃圾
当一个对象没有任何的变量或属性对它进行引用,此时我们将永远无法操作该对象,垃圾就是这样产生的
在JS中有自己的自动垃圾回收机制,会自动将这些垃圾对象从内存中销毁,我们不需要也不能进行垃圾回收的操作
我们需要做的就是将不再用的对象设置为null即可