目录
一、对象
1.1.什么是对象?
通俗的讲,对象就是一种复合的数据类型,在对象中可以保存多个不同数据类型的属性。
1.2.对象的好处
当我们把一些不同属性放到同一个对象中时,这些属性就有了一个共同的关系,都同属于某一个对象,管理起来也就更加方便,而且,多多使用对象时,我们的代码中的数据会变得更为简洁。
1.3.对象的分类
1.3.1.内建对象
由ES标准定义的对象,在任何的ES的实现中都可以使用。
比如,Math、String、Number、Boolean、Function、Object......
1.3.2.宿主对象
由js的运行环境提供的对象,主要指由浏览器提供的对象
比如,BOM、DOM中的对象。举例,document、console等
1.3.3.自定义对象
开发人员自己创建的对象
1.4.对象的基本操作
1.4.1.创建对象
1.使用new关键字调用的函数是构造函数,构造函数是专门用来创建对象的函数。
2.使用对象字面量来创建一个函数,使用对象字面量可以创建对象的时候直接指定对象属性。指定属性时,属性名和属性值之间通过:连接,多个名值对之间通过,隔开,最后一个属性后不要写,
var obj = new Object()
var obj = {}
1.4.2.对象属性(增)
在对象中保存的值称为属性,向对象中添加属性。我们可以认为 添加就是另类的修改。
对象.属性名 = 属性值
obj.name = 'hello'
1.4.3.读取对象属性(查)
保存在对象中的属性,我们需要对其进行读取,才能知道保存的是什么。当我们读取对象中没有的属性是,返回undefined,并不报错
对象.属性名
console.log(obj.name)
1.4.4.修改对象属性(改)
对象.属性名 = 属性值
obj.name = 'jack'
1.4.5.删除对象属性(删)
delete 对象.属性名
delete obj.name
1.5.属性名和属性值
向对象中添加属性时,我们就需要告诉这个对象,我们就要提供属性名和属性值。在此例中,name就是属性名,'hello'就是属性值。
obj.name = 'hello'
1.5.1.属性名
1.属性名不强制要求遵循标识符的规范,但是一般来说,尽量使用该规范
2.当我们使用的属性名比较特殊时,比如是一个数字串,我们就需要使用另外一种方式来为对象添加属性。读取时也需要采用这种方式。
3.使用[]这种方式使用我们的属性更加灵活,因为在[]中可以传递一个变量。我们可以通过控制变量的值来读取不同的属性。
对象['属性名'] = 属性值
obj['123'] = 'hello'
1.5.2.属性值
1.js的属性值,可以是任意的数据类型,甚至可以是一个对象,或者是一个函数
1.6.in运算符
主要用于检查一个对象中是否含有指定的属性。有,返回true;没有返回false
'属性名' in 对象名
'name' in obj
二、函数
2.1.什么是函数?
函数也是一个对象。
函数中可以封装一些功能代码,在需要使用该功能的时候就调用该函数来执行这些代码
2.2.函数的基本使用
2.2.1.创建函数
注意我们此时虽然创建了函数,但是并没有使用,所谓的创建函数,本质上就是把一个函数保存了起来。
方式一:通过new关键字来创建函数
const 函数名 = new Function()
const fun = new Function(alert("hello,函数"));
方式二:使用函数声明来创建一个函数
function 函数名([形参1, 形参2,...]){
执行代码
};
方式三:使用函数表达式来创建一个函数
const 函数名 = function ([形参1, 形参2,...]) {
执行代码
};
2.2.2.调用函数
封装到函数中的代码不会立即执行,而是在函数被调用的时候才会执行。
fun([实参1, 实参2,...]);
函数基本使用举例
function Person(name , age , gender){
this.name = name;
this.age = age;
this.gender = gender;
}
Person("孙悟空" , 18 , "男");
2.2.3.调用函数的其他方法
call和apply
当对函数调用这两个方法时,都会调用函数执行
不同的是,在调用call()和apply()时,可以将一个对象指定为第一个参数此时,这个对象就是成为执行函数时的this(转接本文的2.11)
call()方法可以将实参在对象之后依次传递
apply()方法需要将实参封装到一个数组中统一传递
fun();//fun是一个函数,普通调用
fun.call();
fun.apply();//此时三者相同
//普通调用和两者的区别
fun();
fun.call(obj);//this指向obj
fun.apply(obj2); //this指向obj2
obj.sayName.apply(obj2);//this指向obj2
//call和apply之间的区别
fun.call(obj);
fun.apply(obj);
fun.call(obj,1,2);
//fun.apply(obj,1,2);//报错,apply()方法不能以这种形式调用参数
fun.apply(obj,[1,2]);
2.3.函数的参数
2.3.1.形参
我们在定义函数的时候可以在函数的()中指定一个或多个形参,多个形参之间通过,隔开。所谓的形参相当于是在函数内部声明了没有赋值的变量,但是形参是在调用函数的时候赋值。
2.3.2.实参
我们在调用函数的时候,可以在函数的()中指定实参,实参会对应地赋值给函数中对应的形参。值得注意的是,
1.我们在调用函数的时候不会检查实参的类型,因此,我们需要注意不要给函数传递一个非法的参数,此时,我们也许需要进行类型检查。
2.调用函数时也不会检查实参的数量,多余的实参不会被赋值使用;但是如果,实参数目少于需要的形参数时,他就会把其余没有对应实参的形参值设置为undefined
3.函数的实参可以是任何类型的函数,包括对象,或是另一个函数。
2.4.函数参数的对象写法
2.4.1.为什么要使用对象作为参数?
因为我们的形参有的时候并不是一个两个,有时会出现多个形参的情况,而这个时候,我们在调用函数的时候,我们传递的实参必须是跟形参一一对应的,否则就会出现预期之外的结果,这个时候,我们就可以通过传递对象作为参数的方法来解决
2.4.2.对象参数的基本使用
当我们使用对象作为参数的时候,我们在函数中的代码都是围绕传递进来的对象的属性来编写的,这样,当我们传递的对象实参到函数中时,他们会对应调用自己的属性放到对应的位置
对象参数的举例
function Person (o) {
this.name = o.name
this.age = o.age
this.gender = o.gender
}
let per = {
name: '孙悟空',
age: 18,
gender: 80
}
Person(per)
2.5.函数的返回值
我们往往执行一个函数的时候,是需要将结果返回用来进行下一步计算的,因此,我们的函数一般需要一个返回值。我们可以使用return语句来设置函数的返回值
1.return后的值就是就是函数经过运算后返回出来的结果。
2.return后面可以跟任何类型的值,包括对象也可以。
3.return也有类似break的功能,retrun语句会退出整个函数。
4.return后面不跟任何值的时候,该函数会返回一个undefined,相当于return undefined。不写return语句也是一样的,返回undefined
function Person (a, b) {
let result = a + b;
return result;
}
2.6.立即执行函数
当我们创建的函数在创建的时候就执行时,这种函数被称为立即执行函数。我们可以通过()来实现一个立即执行函数。这种函数往往只会执行一次。
(function () {
console.log('a')
})()
2.7.方法
函数本身是可以作为对象的一个属性来保存的,此时,我们成这个函数是这个对象的一个方法,调用函数就说明调用对象的这个方法。
1.本质上和调用函数并无区别,是名称上的不同。
2.调用方式为 对象名.方法名()
//sayName函数
const sayName = function (name) {
console.log(name)
}
const obj = {
name: 'jack',
//obj的sayName方法
sayName: function (name) {
console.log(name)
}
}
2.8.枚举对象中的属性
我们可以使用forin语句来枚举对象中的属性,具体forin可以参考我上一篇javascript笔记01
JavaScript学习笔记01-数据类型-运算符-循环语句_czttaotao的博客-CSDN博客
注意:只需要把文章中对象的键理解成属性名即可。
2.9.作用域
作用域指的是一个变量的作用范围。js中共有全局作用域和局部作用域两种。
2.9.1.全局作用域
1.直接编写在script标签中的js代码,都在全局作用域
2.全局作用域在页面打开时候创建,在页面关闭的时候销毁
3.在全局作用域中,有一个全局对象window,我们可以直接使用
它代表的就是浏览器的窗口,由浏览器直接创建
4.在全局作用域中,我们创建的变量都会作为window对象的属性保存。
5.在全局作用域中,我们创建的函数都会作为window对象的方法保存。
6.全局作用域中的变量都是全局变量,在页面的任意部分都可以访问到
小技巧:
当我们没有在全局作用域中声明变量c的时候,输出一个window.c的时候,我们发现输出为undefined,输出c则会报错。这是因为,我们输出window.c本质上是输出window对象的c属性,属性没有时会返回undefined。但是输出c本质上实在输出变量c,此时没有则会报错。
2.9.2.局部作用域
调用函数时创建函数作用域,函数执行完毕以后,该作用域就被销毁。
1.每调用一次函数,就会创建一个新的函数作用域,互相独立
2.在局部作用域中可以访问到全局作用域中的变量。全局作用域中不能访问局部作用域变量
3.当在局部作用域中操作一个变量的时候,会先在自身作用域中寻找,有,直接使用;没有,去上一级作用域寻找,直到,找到全局作用域。
4.当我们想要直接访问全局变量时,可以使用window对象来实现
5.在函数作用域中也有声明提前的特性
6.在函数中,不使用var声明的变量都会称为全局变量
2.10.声明提前
2.10.1.变量的声明提前
使用var关键字声明的变量,会在所有的代码执行之前被声明,此时,我们在声明变量代码之前的代码是可以使用这个变量的,值为undefined
console.log(a)//undefined
var a = 10
console.log(a)// 10
不使用var关键字的时候,就不会提前声明了,这样我们在代码前使用该变量就会报错
console.log(a)// 报错
a = 10
console.log(a)// 10
直接声明变量的时候,就相当于是给 window对象添加了一个属性
a = 10
//相当于
window.a = 10
2.10.2.函数的声明提前
我们知道,我们创建函数的时候可以使用函数声明形式创建也可以通过函数表达式的形式来创建,但是这两者也是由区别的
1.通过函数声明形式创建的函数function(){}
它会在所有代码执行之前就被创建,所以我们可以在函数声明前调用。(提前)
//会被提前声明
fun()//被调用
function fun() {
console.log('111')
}
2.通过函数表达式创建的函数,不会被声明提前,所以不能在声明之前调用。
//不会被提前声明
fun()//报错
var fun2 = function () {
console.log('222')
}
2.11.this
解析器在调用函数的时候,每次都会向函数内部传递两个隐含的参数,其中一个参数就是this,this指向的是一个对象。这个对象我们称为函数执行的上下文对象。根据函数的调用方式不同,this的指向也不一样
2.11.1.函数形式
this指向的是window
fun()
2.11.2.方法形式
this就是调用方法的那个对象
obj.fun()
2.11.3.构造函数形式
this就是新创建出来的那个对象
var per = new Person()
2.11.4.call和apply
/这里防止上面需要重复观看,因此,在这里再写一遍/
当对函数调用这两个方法时,都会调用函数执行
不同的是,在调用call()和apply()时,可以将一个对象指定为第一个参数此时,这个对象就是成为执行函数时的this
call()方法可以将实参在对象之后依次传递
apply()方法需要将实参封装到一个数组中统一传递
fun();//fun是一个函数,普通调用
fun.call();
fun.apply();//此时三者相同
//普通调用和两者的区别
fun();
fun.call(obj);//this指向obj
fun.apply(obj2); //this指向obj2
obj.sayName.apply(obj2);//this指向obj2
//call和apply之间的区别
fun.call(obj);
fun.apply(obj);
fun.call(obj,1,2);
//fun.apply(obj,1,2);//报错,apply()方法不能以这种形式调用参数
fun.apply(obj,[1,2]);
2.12.使用工厂方式创造对象
当我们需要创建多个类似的对象的时候,我们以前是需要写多个相同的代码,但是我们可以通过工厂方法来创建对象来让我们的代码更为简洁。
思路:其实就是把封装一个函数,我们在这个函数中创建好我们想要的对象,然后把这个对象当作函数的返回值。此时,我们就就可以通过这个函数来创建我们想要的对象。
function createPerson(name, age) {
var obj = new Object();
obj.name = name;
obj.age = age;
return obj;
}
var obj1 = createPerson('孙悟空', 18)
var obj2 = createPerson('猪八戒', 19)
2.13.构造函数创建对象
创建一个构造函数,专门用来创建Person对象的,构造函数就是一个普通的函数,创建方式和普通函数是一样的,不同的是构造函数习惯上首字母大写。
普通函数是直接调用,构造函数需要使用new关键字来调用。
2.13.1.构造函数执行流程
1.立刻创建一个新的对象
2.将新建的对象设置为函数中的this
3.逐行执行函数中的代码
4.将新的对象作为返回值返回
2.13.2.构造函数举例
function Person(name, age) {
this.name = name;
this.age = age;
}
var obj1 = new Person('孙悟空', 18)
var obj2 = new Person('猪八戒', 19)
这里不要把构造函数和工厂方式搞混了,实际上,两者是有区别的,工厂方式只是普通函数返回值的一种使用方式, 而构造函数则是函数的另外一种结构了。从代码中也可以看到,从代码量上无疑是构造函数比较方便。
2.13.3.什么是实例?
我们把通过构造函数创建的一个对象称为是该类的一个实例。
比如,上面的obj1和obj2就分别是Person类的一个实例
2.13.4.实例检测instanceof
我们可以使用instanceof来检查一个对象是否是一个类的实例,
语法:
对象 instanceof 构造函数名
如果是,返回true;否则返回false
console.log(obj1 instanceof Person)
注意:
1.所有的对象都是Object后代,因此任何对象和Object做instanceof做回返回true
2.14.构造函数补充
当我们创建的构造函数中,可以为对象实例添加一个方法。
function Person(name, age) {
this.name = name;
this.age = age;
this.sayName = function() {
console.log(this.name)
}
}
这个方法是在构造函数中创建的,所以,构造函数每执行一次,就会创建一个新的同样的方法,在每个实例中,这个方法都是唯一且相同的
问题:
我们每创建一个对象实例,都会在对象中创建一个新的方法,这个方法是一样的,这就会造成一定的内存空间的浪费。
思路:
我们需要让这些实例使用同一个方法,这样就避免了空间的浪费
解决:
我们可以在构造函数外面或全局作用域中创建一个函数来实现这个方法,在构造函数中,是可以访问到这个方法的,把这个方法赋值给函数的其中一个变量,此时,我们变量中存储的就是我们创建的函数的内存地址了。
function Person(name, age) {
this.name = name;
this.age = age;
//向对象添加一个方法
this.sayName = fun;
}
function fun() {
console.log(this.name)
}
2.15.原型对象
2.15.1.引出原型对象
问题:
在2.14中我们把函数定义到了全局作用域,这会导致一些问题,比如,函数名重名等问题
思路:
我们不要在全局作用域中创建该函数,而是在一个其他的专属于这个构造函数的地方创建按这个函数
解决:
这个问题可以通过我们的原型来解决
2.15.2.prototype
我们创建的每一个函数,,解析器都会像函数中添加一个属性prototype,这个属性对应一个对象,这个对象就是我们所谓的原型对象。
如果函数作为普通函数调用prototype没有任何作用,但是,当函数以构造函数调用时,它所创建的对象实例都会有一个隐含的属性,指向的是这个构造函数的原型对象,我们可以通过__proto__来访问该属性
本质上说,就是说,每一个构造函数都有自己独有的prototype属性,这个属性对应的是一个对象,通过这个构造函数创建的对象实例,都可以通过__proto__来访问该属性。
这样我们就可以把对象共有内容都放到这个原型对象中,让对象实例,通过__proto__来调用这个原型对象。
function Person(name, age) {
this.name = name;
this.age = age;
}
//在原型中添加方法
Person.prototype.sayName = function () {
console.log(this.name)
}
var obj1 = new Person('孙悟空', 18)
console.log(obj1.__proto__ == Person.prototype)//true
obj1.sayName()//'孙悟空'
总结:
我们在创建构造函数的时候,可以把对象共有方法统一放到原型对象中使用。这样不仅,节省了我们的空间,而且,不会影响到全局作用域的变量。
2.15.3.对象实例的执行流程
(后接2.15.4的第2点)
当我们访问对象实例的一个属性或方法的时候,他会先在对象本身寻找,当找不到的时候,则会在原型对象中寻找,找到就直接使用。
2.15.4.原型对象补充
1.当我们使用in检查对象中是否含有某个属性的时候,如果对象中没有但是原型中有这个属性,那么也会返回true
console.log('name' in obj1)
解决办法:
我们可以通过使用对象的hasOwnProperty()来检查对象自身是否函式该属性或方法,使用该方法的时候只有当对象本身有该属性或方法时才返回true
console.log(obj1.hasOwnProperty('name'))
2.原型对象也是对象,他也有原型。
所以,我们实际的执行流程是:当我们使用一个对象的方法或属性时,先在自身寻找;找不到,在自己的原型对象中寻找;找不到,会在自己原型对象的原型对象中寻找,一层一层网上找,直到找到Object对象的原型,找不到,返回undefined。
3.Object的原型没有原型,对应的值为null。也就是说,当我们一层层网上找的时候,找到一个值为null的时候,就说明找到Object了。
2.16.toString和console.log
了解,也许已经修复了
当我们使用console.log在页面中打印一个对象的时候,实际上输出的是对象的toString方法的返回值。
什么意思呢?
就是说,我们在控制台看到的打印结果,是我们要打印的结果使用了toString方法之后的返回值。而不是数据本身。
console.log(obj1)
console.log(obj1.toString())
//两者相同(可能已经解决了,我在谷歌、火狐、Edge都测试的不对)
如果我们希望在输出对象时输出对象的详细信息,我们就需要通过重写toString()方法来实现
我们通过上面创建的构造函数举例
function Person(name, age) {
this.name = name;
this.age = age;
}
//在原型中添加toString方法
Person.prototype.toString = function () {
return 'Person[name=' + this.name + ',age=' + this.age + ']'
}
2.17.垃圾回收
当个对象没有任何变量或属性对它进行引用,此时我们不能操作该对象,那么这种对象就是一个垃圾,这种对象回国多占用内存空间,导致程序运行缓慢
js中拥有自动垃圾回收机制,会自动将这些垃圾对象从内存中销毁,我们不需要也不能进行操作。
我们需要将我们以后不用的变量或属性设置为null
2.18.函数补充arguments
前面我们也提到了,解析器在执行函数的时候会传递两个隐含参数,其中一个是this,另外一个就是我们的arguments
在调用函数时,浏览器会传递两个隐含的函数
1.函数的上下文对象this
2.封装实参的对象arguments
2.18.1.arguments是什么?
1.arguments是一个类数组对象,也可以通过索引来操作数据,也可以获取长度。
2.在调用函数时,我们传递的实参都会在arguments中保存。
arguments.length可以用来获取实参的长度
2.18.2.argumens使用实参
我们即使不定义形参。也可以通过arguments来使用实参。但是这种方法比较麻烦
arguments[0]表示第一个实参
arguments[1]表示第二个实参
也就是说,我们传递的实参数目较多时,我们可以通过argumments来获取多出来的那部分实参来进行使用。
2.18.3.arguments的callee属性
arguments中还有一个callee属性,这个属性对应了一个函数对象,就是当前正在执行的函数的对象
function fun() {
console.log(arguments);
//两种检查其是不是数组Array的方法
console.log(arguments instanceof Array);
console.log(Array.isArray(arguments));
//获取arguments的长度
console.log(arguments.length);
//确定一下arguments是否含有callee属性
console.log(arguments.callee);
console.log(arguments.callee == fun);
};
fun(1,2,"true");