对象
除了5种基本的数据类型(String 字符串、Number 数值、Boolean 布尔值、Undefined 未定义、Null 空值),其它的全都是对象(object)。
基本数据类型都是单一的值, 比如: "itlike" , 123 , true等, 值和值之间没有任何的联系。
对象属于一种复合的数据类型,在对象中可以保存多个不同数据类型的属性。
对象分类:
对象分类 | 说明 |
内建对象 | 由ES标准中定义的对象,在任何的ES的实现中都可以使用,比如:String Number Boolean Function Object Math .... |
宿主对象 | 由JS的运行环境提供的对象,现在主要指由浏览器提供的对象,比如: BOM对象, DOM对象 |
自定义对象 | 自己创建的对象,比如: Person, Dog, .... |
常见的创建对象的方式:
(1)对象字面量---一般用于数据传递
使用对象字面量,可以在创建对象时,直接指定对象中的属性。【var 对象 = {属性名:属性值,属性名:属性值....};】
对象字面量的属性名可以加引号也可以不加,建议不加, 如果要使用一些特殊的名字,则必须加引号。
属性名和属性值是一组一组的名值对结构,名值之间使用:连接,多个名值对之间使用,隔开
var person = {
name: "xbd",
age: 20,
friends: ["dgou", "tong"],
eat: function() {
console.log("eat");
}
};
console.log(typeof person); //object
console.log(person);
person.name = "张三"; //修改name属性
console.log(person.name);
person.eat();
// 以下对数组进行遍历的方式不适用于对象遍历。
/*for(var i=0; i<person.length; i++){
console.log(person[i]);
}*/
// 遍历对象
for (var key in person) {
console.log(key);
console.log(person[key]);
}
运行结果:
(2)new Object()创建对象---缺点:无法量厂
构造函数是专门用来创建对象的函数。使用new关键字调用的函数,可以被称为构造函数(constructor)【var obj = new Object();】
var obj = new Object();
// 属性
obj.name = "xbd";
obj.age = 18;
obj.sex = "female";
console.log(typeof obj)
console.log(obj)
运行结果:
(3)工厂函数创建对象
new Object()---new后面调用函数,我们称为构造函数。Object() 我们把它视为一个构造函数,构造函数的本质就是一个函数,只不过构造函数的目的是为了创建新对象,为新对象进行初始化(设置对象的属性)。
通过工厂方法可以大批量的创建对象,使用工厂方法创建的对象,使用的构造函数都是Object,所以创建的对象都是Object这个类型,就导致我们无法区分出多种不同类型的对象。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
function Dog(name, age, dogFriends) {
var d = new Object(); //原料
// 属性
d.name = name; //加工
d.age = age;
d.dogFriends = dogFriends;
d.doSomething = function() {
console.log("do something")
};
return d; //出厂
}
var d1 = Dog("小花", 1);
var d2 = Dog("大花", 5, ["大大", "小小"]);
console.log(d1);
d1.doSomething();
console.log(d2);
console.log(d1 === d2);
</script>
</body>
</html>
在浏览器中打开,点击f12,查看Console:
(4)自定义构造函数
构造函数就是一个普通的函数,创建方式和普通函数没有区别, 不同的是构造函数习惯上首字母大写。
构造函数和普通函数的区别就是调用方式的不同,普通函数是直接调用,而构造函数需要使用new关键字来调用。
构造函数执行流程:
- 1. 立刻创建一个新的对象
- 2. 将新建的对象设置为函数中this, 在构造函数中可以使用this来引用新建的对象
- 3. 逐行执行函数中的代码
- 4. 将新建的对象作为返回值返回
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
// 1. 构造函数
function Dog(name, age, dogFriends) {
// 1.1 属性
this.name = name;
this.age = age;
this.dogFriends = dogFriends;
// 1.2 方法
this.eat = function(someThing) {
console.log(this.name + "在吃" + someThing);
};
this.run = function(someWhere) {
console.log(this.name + "跑" + someWhere);
}
}
// 小狗
var smallDog = new Dog("小花", 1);
smallDog.age = 2;
console.log(smallDog);
smallDog.eat("奶");
smallDog.run("广场");
// 大狗
var bigDog = new Dog("大花", 5, ["大大", "小小"]);
console.log(bigDog);
console.log(bigDog === smallDog);
</script>
</body>
</html>
在浏览器中打开,点击f12,查看Console:
属性和方法:
属性:一般是名词,用来描述事物的特征。如果一个变量属于一个对象所有,那么该变量就可以称之为该对象的一个属性。
方法:动词,描述事物的行为和功能。如果一个函数属于一个对象所有,那么该函数就可以称之为该对象的一个方法。
对象的属性值可以是任何的数据类型,也可以是个函数。如果一个函数作为一个对象的属性保存,那么我们称这个函数时这个对象的方法, 调用这个函数就说调用对象的方法(method)
遍历对象所有属性:for ... in 语句【for(var 变量 in 对象){ }】,对象中有几个属性,循环体就会执行几次。
new关键字:
一般和构造函数配合使用。构造函数首字母必须大写,构造函数必须和new一起使用。
new做了哪些事?
- new先在内存中创建一个新的空对象;
- new会让this指向这个新对象;
- 执行构造函数,给这个新对象添加属性和方法;
- 返回这个新对象。
this关键字:
解析器在调用函数每次都会向函数内部传递进一个隐含的参数, 这个隐含的参数就是this,this指向的是一个对象 , 这个对象我们称为函数执行的上下文对象。
根据函数的调用方式的不同,this会指向不同的对象。
- (1)普通函数执行,以函数的形式调用时,this永远都是window。
- (2)如果函数作为一个对象的方法,以方法的形式调用时,this就是调用方法的那个对象。
- (3)当以构造函数的形式调用时,this就是新创建的那个对象,。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
//1.普通函数执行
var str = "aaa"
function func() {
console.log(this)
console.log(this.str)
}
func();
console.log("====分割线===")
//2.对象
var obj = {
name: "xbd",
func: function() {
console.log(this)
console.log(this.name)
}
}
obj.func();
console.log("====分割线===")
//3.构造函数
function Func() {
this.name = "xbd";
this.age = 12;
console.log(this);
console.log(this.name)
}
new Func();
</script>
</body>
</html>
在浏览器中打开,点击f12,查看Console
基本类型和引用类型在堆栈中的表示?
JS中的变量都是保存到栈内存中的,基本数据类型的值直接在栈内存中存储,值与值之间是独立存在,修改一个变量不会影响其他的变量。
对象是保存到堆内存中的,每创建一个新的对象,就会在堆内存中开辟出一个新的空间,而变量保存的是对象的内存地址(对象的引用),如果两个变量保存的是同一个对象引用,当一个通过一个变量修改属性时,另一个也会受到影响。
比较两个基本数据类型的值时,就是比较值。比较两个引用数据类型时,比较的是对象的内存地址,如果两个对象是一模一样的,但是地址不同,它也会返回false。
函数
定义:函数是一个对象,函数是可以重复执行的代码块;是完成特定功能的一段代码;使用typeof检查一个函数对象时,会返回function
作用:因为一部分代码使用次数可能会很多,所以封装起来,需要的时候调用就可以了。
特点:(1)封装到函数中的代码不会立即执行(2)函数中的代码会在函数调用的时候执行(3)调用函数 语法:函数对象()(4)调用函数时,函数中封装的代码会按照顺序执行。
函数声明的3种方式:
函数声明方式 | function add(num1,num2){ return num1+num2; } | 函数前后均可调用,作用域提升:函数提升 |
函数表达式声明方式 | var add= function(num1,num2){ return num1+num2; }; | 必须在函数后调用,作用域提升:变量提升
|
使用Function构造函数 | var add = new Function('num1','num2','return num1+num2'); | 不推荐使用, 主要用于面向对象时理解"函数就是对象, 函数名就是指针"这一概念 |
JavaScript解析器首先会把当前作用域的函数声明提前到整个作用域的最前面。
// 使用函数声明方式,可以在任意地方调用函数,add函数提升到最前面
// function add(a, b) {
// return a + b;
// }
// 使用函数表达式声明方式,必须在该函数后进行调用。因为只是add变量提升到最前面。
var add = function(a, b) {
return a + b;
}
console.log(add(1, 3));
函数的参数:
参数的作用:为了增强函数的功能性和函数的可拓展性,便于交互。
形参:形式上参与运算的变量,无实际值,为实参占位置,就像一个躯壳一样。【function add(a,b){} //a,b是形参,占位用,函数定义时形参无值】
实参:实际参与运算的变量。形参为他占位置,真实参与运算的变量。add(x, y); //x, y实参,有具体的值,会把x, y复制一份给函数内部的a和b,函数内部的值是复制的新值,无法修改外部的x,y】
注意:在其它语言中实参个数必须和形参个数一致,但是JavaScript中没有函数签名的概念,实参个数和形参个数可以不相等。
函数使用注意:
- 在调用函数时,可以在()中指定实参, 实参将会赋值给函数中对应的形参。
- 调用函数时解析器不会检查实参的类型, 所以开发中一般需要对参数进行类型的检查。
- 函数的实参可以是任意的数据类型。
- 调用函数时,解析器不会检查实参的数量, 多余实参不会被赋值, 如果实参的数量少于形参的数量,则没有对应实参的形参将是undefined。
arguments对象
包含了传入函数中的所有参数, arguments并不是一个数组,只是与数组相似, 除了拥有length属性,数组的所有属性和方法都不具备。
arguments对象还有一个名叫callee的属性, 该属性是一个指针, 指向拥有这个arguments对象的函数;
function sum(num1, num2) {
// arguments对象
console.log(arguments);
var value = 0;
for (var i = 0; i < arguments.length; i++) {
value += arguments[i];
}
console.log("所有数的总和为:", value);
console.log("arguments.length=", arguments.length);
console.log("sum.length=", sum.length);
}
sum(10, 20, 30);
执行结果
函数返回值(return)
当一个函数被调用,通常会从函数的开始执行到结束。如果想提前结束该函数的执行可以使用return语句,return语句后面的所有语句将永远不会执行。一般return用于返回结果。
注意:如果函数没有显示的使用 return语句 ,那么函数有默认的返回值:undefined;如果函数使用 return语句,那么跟在return后面的值,就成了函数的返回值;如果函数使用 return语句,但是return后面没有任何值,那么函数的返回值也是:undefined;推荐的做法是要么让函数始终都返回一个值,要么永远都不要返回值。
匿名函数
没有命名的函数【function () {}】
作用
(1)用在绑定事件的时候
document.onclick = function () {
alert(1);
}
(2)定时器
setInterval(function () {
console.log(444);
},1000);
(3)函数定义完,立即被调用,这种函数叫做立即执行函数,立即执行函数往往只会执行一次
(function(num1, num2){
console.log("mum1 = "+ num1);
console.log("num2 = "+ num2);
})(2, 5);
回调函数:
回调函数就是一个通过函数调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。
function fn(num1, num2, operation) {
return operation(num1, num2);
}
//定义加减乘除
function add(a, b) {
return a + b;
}
function sub(a, b) {
return a - b;
}
function mul(a, b) {
return a * b;
}
function del(a, b) {
return a / b;
}
console.log(fn(10, 2, add));
console.log(fn(10, 2, sub));
console.log(fn(10, 2, mul));
console.log(fn(10, 2, del));
回调函数经典案例:求Fibonacci的第n个数1 1 2 3 5 8 13 21...
function f1 (n) {
if (n == 1) return 1;
if (n == 2) return 1;
return f1(n-1) + f1(n-2);
}
console.log(f1(5));
回调函数经典案例:求n个数的累加
function getSum (n) {
if (n == 1) { return 1;}
return n + getSum(n - 1);
}
console.log(getSum(100));
注意:执行以下代码,输出【NaN】,因为【sum(10)】的两个参数分别是10和undefined,10+undefined=NaN
function sum(num1, num2) {
console.log(num1 + num2);
}
sum(10);
变量的作用域:
块级作用域:在其它语言中,任何一对花括号中的语句都属于一个块,在这之中定义的所有变量在代码块外都是不可见的。
全局变量:定义在script或者不属于某个函数的变量
局部变量:定义在函数内部的变量
注意:函数内部可以访问到该函数所属的外部作用域的变量(作用域链);不使用var声明的变量是全局变量;变量退出作用域之后会销毁,全局变量关闭网页或浏览器才会销毁
变量提升:
- 变量提升:定义变量的时候,变量的声明会被提升到作用域的最上面,变量的赋值不会提升。
- 函数提升:JavaScript解析器首先会把当前作用域的函数声明提前到整个作用域的最前面。
笔试题
var num = 10;
fun();
function fun() {
console.log(num);
var num = 20;
}
运行代码输出结果【undefined】由于fun()函数中已声明了【var num=20】执行到【console.log(num)】时,函数内部的变量声明被提升,赋值未被提升。局部变量, 先在函数内部的作用域找变量num,如果找到则使用,如果找不到再去父级作用域找num变量。
var a = 18;
f1();
function f1() {
var b = 9;
console.log(a);
console.log(b);
var a = '123';
}
运行代码输出结果
数据类型和内存分析:
- 栈区(stack)-----由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。
- 堆区(heap) — 一般由程序员分配释放, 若开发者不释放,程序结束时可能由OS回收 。
简单(基本)数据类型:Number、String、Boolean、Undefined、Null,直接存储值,存放在栈内存中,占据固定大小的空间,
是按值来访问的。
复杂(引用)数据类型:Object、Array、Date.....,存放在堆内存中的对象,变量实际保存的是一个指针,这个指针指向另一个位置。每个空间大小不一样,要根据情况开进行特定的分配。当我们需要访问引用类型的值时,首先从栈中获得该对象的地址指针,然后再从堆内存中取得所需的数据。栈内存中存放地址指向堆内存中的对象。是按引用访问的。
基本类型与引用类型最大的区别实际就是传值与传址的区别。
数组
简单(基本)数据类型只能存储一个值,想要存储多个值时可以使用数组。 数组是一种复杂(引用)数据类型, 属于对象。它和普通对象功能类似,也是用来存储一些值的。不同的是普通对象是使用字符串作为属性名的,而数组时使用数字来作为索引操作元素。数组的存储性能比普通对象要好,在开发中我们经常使用数组来存储一些数据。数组存储的数据可以是任何类型(数字、字符、布尔值等),但一个数组中应该只存一种类型的变量。
数组创建:
(1)使用 Array 构造函数创建数组
- 创建一个空数组【var arr=new Array();】
- 创建一个长度为10的数组【var arr=new Array(10);】
- 创建包含数字/字符串的数组【var arr=new Array(12, 5, 8, 9);】【var arr=new Array(“xiao”,"cx");】
(2)使用数组字面量创建数组
- 创建一个空数组【var arr=[ ];】
- 创建包含数字/字符串的数组【var arr=[12, 5, 8, 9];】【var arr=[“xiao”,"cx"];】
以上两种数组定义方式没有任何差别,[ ]的性能略高,因为代码短
属性:length--既可以获取,又可以设置【console.log(arr.length)】;可以通过修改数组的长度来改变数组中元素的个数,如果改小了,数组从后面删除元素。(伪数组的长度可以修改,但是不能修改里面的元素)
获取数组中的元素:数组中的指定元素 = 数组名[索引值];数组的索引代表的是数组中的元素在数组中的位置,从0开始。如果获取数组中元素是,数组名[索引值],没有指定索引(元素没那么多),系统不报错,而是给定值为undefined;
方法:
添加 | push(元素),从尾部添加,返回修改后数组的长度; unshift(元素),从头部添加,返回修改后数组的长度。 var arr=[1,2,3]; console.log(arr.push(5));//4 arr.unshift(5); |
删除 | pop(),从尾部弹出,返回移除的项; shift(),从头部弹出,返回删除元素的值 var arr=[1,2,3]; arr.pop(); arr.shift(); |
排序 | sort([比较函数]),排序一个数组、字符串数组、数字数组 ar arr=['float', 'zindex', 'xy', 'absolute', 'blue', 'leo']; arr.sort();
var arr=[96, 8, 12, 72, 33, 118]; arr.sort(function (num1, num2){ return num1-num2;//从小到大排序 });
注意【return num1-num2;】改为【return num2-num1;】则为从大到小排序。 |
查找位置 | indexOf() ---接收两个参数:要查找的项和(可选的)表示查找起点位置的索引。其中, 从数组的开头(位置 0)开始向后查找。 --arr.indexOf(4) lastIndexOf()---接收两个参数:要查找的项和(可选的)表示查找起点位置的索引。其中, 从数组的末尾开始向前查找。--arr.lastIndexOf(4,4) |
转换类 | reverse()---翻转数组项的顺序--arr.reverse() concat(数组2)---连接两个数组--arr1.concat(arr2) join(分隔符)---用分隔符,组合数组元素,生成字符串 var arr=[1,2,3,4]; alert(arr.join(':')); //1:2:3:4 字符串split |
插入、删除 splice
| (1)中间删除:splice(开始,长度)--arr.splice(2, 3); (2)中间插入:splice(开始, 长度,元素…)--arr.splice(5, 0, 'a', 'b', 'c');先删除,后插入 (3)替换:splice(开始, 长度, 元素…)--arr.splice(1, 2, 'a', 'b'); 注意:区别于slice()---slice() 方法可从已有的数组中返回选定的元素.arr.slice(start,end)--返回一个新的数组,包含从 start 到 end (不包括该元素)的 arr 中的元素 (4)清空数组:arr.splice(0); |
遍历 | forEach()---这个方法只支持IE8以上的浏览器, 所以如果需要兼容IE8,则不要使用forEach, 还是使用for循环来遍历。 forEach() 方法用于调用数组的每个元素,并将元素传递给回调函数。forEach()方法需要一个函数作为参数,数组中有几个元素, 函数就会执行几次,每次执行时,浏览器会将遍历到的元素以实参的形式传递进来,我们可以来定义形参,来读取这些内容。浏览器会在回调函数中传递三个参数:当前正在遍历的元素,当前正在遍历的元素的索引,正在遍历的数组 arr.forEach(function(value , index , obj){ console.log(value); }); 【 var arr = [1, "deef", 5, 30, "dd"]; arr.forEach(function(value, index) { console.log(index + ":" + value); });】 |
其他
| map() 方法“映射”,对数组中的每一项运行给定函数,返回每次函数调用的结果组成的数组。 【 var arr = [1, 3, 5]; var arr2 = arr.map(function(value, index) { return value * value + 1; }); console.log(arr2); //[2,10,26]】 filter() “过滤”功能,数组中的每一项运行给定函数,返回满足过滤条件组成的数组。 【 var arr = [1, 2, 6, 8, 9, 10, 11]; var arr2 = arr.filter(function(value, index) { return value % 2 === 0 || value > 9; }); console.log(arr2); //[2,6,8,10,11]】 every() 方法用于检测数组所有元素是否都符合指定条件(通过函数提供)如果数组中检测到有一个元素不满足,则整个表达式返回 false ,且剩余的元素不会再进行检测。如果所有元素都满足条件,则返回 true。 【 var arr = [11, 2, 3, 4, 15]; var result = arr.every(function(value, index) { return value < 10; }); console.log(result);//false 】 some() 方法用于检测数组中的元素是否满足指定条件(函数提供),如果有一个元素满足条件,则表达式返回true , 剩余的元素不会再执行检测。如果没有满足条件的元素,则返回false 【var arr = [31, 25, 36, 34, 13]; var result = arr.some(function(value, index) { return value < 20; }); console.log(result); //true 】 |
示例1.求一组数中的最大值和最小值,以及所在位置
// 1. 定义变量
var array = [11, 23,-34,-11,55];
var maxValue = array[0], minValue = array[0]; // 最大值 和 最小值
var maxIndex = 0, minIndex = 0; // 最大索引 和 最小索引
// 2. 遍历数组
for(var i=1; i<array.length; i++){
// 如果数组中的元素大于我们定义的初始值
if(array[i] > maxValue){
// 把这个元素赋值给最大值, 并把索引改变
maxValue = array[i];
maxIndex = i;
}
if(array[i] < minValue){
minValue = array[i];
minIndex = i;
}
}
console.log("最大值为:",maxValue,"它的索引值是:",maxIndex);
console.log("最小值为:",minValue,"它的索引值是:",minIndex);