第2章 JavaScript数据类型--2(复杂数据类型,即对象、函数、数组;typeof和思维导图)读书笔记

目录

2.6 对象

2.7函数

2.7.1 概述

2.7.2 函数的属性和方法

2.7.3 函数作用域

2.7.4 参数

2.7.5  闭包

2.7.6 立即调用函数表达式

2.7.7  eval命令(不推荐使用,省略。。。)

2.8 数组

2.9 typeof操作符

2.10 思维导图(本地的时候的图片好好的,上传就这样了。。。。看我打印出来的,好看多了。。。。。)


2.6 对象

1 概述

1)概念

对象就是一组键值对key-value)的集合,是一种无序的复合数据集合。

2)键名,也叫属性/方法(值为函数时)

键名都是字符串(ES6 又引入了 Symbol 值也可以作为键名);

如果键名是数值,会被自动转为字符串

如果键名不符合标识名的条件(比如第一个字符为数字,或者含有空格或运算符),且也不是数字,则必须加上引号,否则会报错

对象的每一个键名又称为“属性”(property),它的“键值”可以是任何数据类型。如果一个属性的值为函数,通常把这个属性称为“方法”;如果属性的还是一个对象,就形成了链式引用

属性可以动态创建,不必在对象声明时就指定。

"use strict";
var obj = {
   
"var": "hello"
    1e2: "23",
   
3.2: "23",
   
0X78: 34,
   
1: "abu",   //如果键名是数值,会被自动转为字符串。
    //1p: "hello",  //报错。
    "1p":"hello",   //不报错
    p: function (x) {   //如果对象的属性是函数,通常称p为对象的方法
        return x;
    }
};


var obj2 = {
   
bar: "wo"
};
obj.foo = obj2;
console.log(obj.foo.bar);  //wo。如果属性的值还是一个对象,就形成了链式引用。
obj.my = 123;   //属性可以动态创建,不必在对象声明时就指定。
console.log(obj.my);   //123

3) 表达式还是语句?(最好看原文)

如果行首是一个大括号,它到底是表达式还是语句?为了避免这种歧义,JavaScript 引擎的做法是,如果遇到这种情况,无法确定是对象还是代码块,一律解释为代码块

如果行首是一个大括号且要解释为对象,最好在大括号前加上圆括号。因为圆括号的里面,只能是表达式,所以确保大括号只能解释为对象

"use strict";
{foo: 123};//第一种可能是,这是一个表达式,表示一个包含foo属性的对象;第二种可能是,这是一个语句,表示一个代码区块,里面有一个标签foo,指向表达式123
console.log({foo: 123});  //{ foo: 123 }   这里不是行首
({foo: 1235});//如果要解释为对象,最好在大括号前加上圆括号。因为圆括号的里面,只能是表达式,所以确保大括号只能解释为对象。
//({console.log(123)});  //报错

//差异在eval语句(作用是对字符串求值)中反映得最明显。
console.log(eval('{foo: 123}'));// 123
console.log(eval('({foo: 123})')) ;// {foo: 123}

 

44)对象的引用

如果不同的变量名指向同一个对象,那么它们都是这个对象的引用,也就是说指向同一个内存地址。修改其中一个变量的属性和方法,会影响到其他所有变量。

2 属性

1)属性的读取

读取对象的属性,有两种方法,一种是使用点运算符,还有一种是使用方括号运算符

注意:如果使用方括号运算符键名必须放在引号里面,否则会被当作变量处理。方括号运算符内部还可以使用表达式。数字键可以不加引号,因为会自动转成字符串。

注意,数值键名不能使用点运算符(因为会被当成小数点),只能使用方括号运算符。

"use strict";
var a = "bar";
var b = "bin";
var obj = {
   
a: 23,
    
bar: 21,
   
b: "nji",
   
1: "ki"
};
console.log(obj.a);   //23  引用对象objfoo属性时,如果使用点运算符,foo就是字符串;
console.log(obj[a]);   //21   如果使用方括号运算符,但是不使用引号,那么foo就是一个变量,指向字符串bar
console.log(obj["a"]); //23
console.log(obj[b]); //undefined   b是变量,指向字符串bin,但是因为在obj对象中没有定义键名为字符串bin的属性,故而为undefined
//console.log(obj.1);   //
数字键不能使用点运算符,这里报错
console.log(obj[1]);  //ki,数字键只能使用放括号运算符,加不加双引号都行
console.log(obj["1"]);  //ki

2)属性的赋值

点运算符和方括号运算符,不仅可以用来读取值,还可以用来赋值。

3)属性的查看

Object.keys()

"use strict";
var obj = {
   
1: 12,
   
bj: "br"
};
console.log(Object.keys(obj));  //[ '1', 'bj' ]。查看一个对象本身的所有属性,可以使用Object.keys方法。

 

4)属性的删除:delete

delete命令只能删除对象本身的属性,无法删除继承的属性。即使delete返回true,该属性依然可能读取到值。

var obj = {
   
1: 12,
   
bj: "br"
};
console.log(Object.keys(obj));  //[ '1', 'bj' ]。查看一个对象本身的所有属性,可以使用Object.keys方法。
console.log(delete obj[1]);  //返回truedelete命令用于删除对象的属性,删除成功后返回true
console.log(Object.keys(obj));  //[ 'bj' ]
console.log(obj[1]);  //undefined
console.log(delete obj[1]);   //返回true 。注意,删除一个不存在的属性,delete不报错,而且返回true

//只有一种情况,delete命令会返回false,那就是该属性存在,且不得删除。
var obj = Object.defineProperty({}, 'p', {
   
value: 123,
   
configurable: false
});

console.log(obj.p); // 123
console.log(delete obj.p );// false。严格模式下会报错

5)属性是否存在:in运算符

in运算符用于检查对象是否包含某个属性(注意,检查的是键名,不是键值)

in运算符的一个问题是,它不能识别哪些属性是对象自身的,哪些属性是继承的。

"use strict";
var obj = {
   
1: 12,
   
bj: "br"
};
//in运算符用于检查对象是否包含某个属性(注意,检查的是键名,不是键值),如果包含就返回true,否则返回false
console.log(1 in obj);  //true
console.log("toString" in obj);  //truein运算符的一个问题是,它不能识别哪些属性是对象自身的,哪些属性是继承的。
//可以使用对象的hasOwnProperty方法判断一下,是否为对象自身的属性。
console.log(obj.hasOwnProperty("toString"));  //false

 

5)属性的遍历  for…in循环

for...in循环用来遍历一个对象的全部属性。

注意:它遍历的是对象所有可遍历(enumerable)的属性,会跳过不可遍历的属性。它不仅遍历对象自身的属性,还遍历继承的属性。对象继承而来的toString属性是不可遍历的。

如想遍历自身属性,for…in结合hasOwnProperty()方法

"use strict";
var obj = {
   
1: 12,
   
bj: "br"
};
for(var key in obj){
   
if(obj.hasOwnProperty(key)){
       
console.log(key,obj[key]);
    }
}

/*输出
1 12
bj br
*/

 

3 with语句(不建议使用)

它的作用是操作同一个对象的多个属性时,提供一些书写的方便。

注意,如果with区块内部有变量的赋值操作,必须是当前对象已经存在的属性,否则会创造一个当前作用域的全局变量。

注意:严格模式已经弃用with语句

//"use strict";
var obj = {
   
1: 12,
   
bj: "br",
   
p1: 1
};
with (obj){
   
p1 = 2;
   
p2 = 3//with区块内部有变量的赋值操作,必须是当前对象已经存在的属性,否则会创造一个当前作用域的全局变量。
}
console.log(obj.p1);  //2
console.log(obj.p2);  //undefined
console.log(p2);  //3
//
另外这是因为with区块没有改变作用域,它的内部依然是当前作用域。这造成了with语句的一个很大的弊病,就是绑定对象不明确。

 

2.7函数

2.7.1 概述

1 函数的声明

1)使用function命令

function 函数名(参数) {
   
//函数体
}

2) 函数表达式:变量赋值的方法

"use strict";
//将一个函数赋值给变量。这时,这个函数又称函数表达式(Function Expression
var myF =function x() {  //一般情况下,函数名可以不用。如果加上函数名,该函数名只在函数体内部有效,在函数体外部无效。
    console.log(typeof x);//一般情况下,
};

3)Function构造函数(基本上不用)

"use strict";
var add = new Function(
   
"x"//
   
"y"//
   
"return x+y"  //除了这个参数是函数体,其他都是add的参数
);
console.log(add(1,2));   //3

2 函数的重复声明

如果同一个函数被多次声明,后面的声明就会覆盖前面的声明。而且,由于函数名的提升(参见下文),前一次声明在任何时候都是无效的。

3

函数调用时使用:圆括号运算符

return语句:函数的返回,遇到return语句,则退出整个函数;如果没有return语句,则返回undefined

递归:函数调用自身

4  函数作为“值”特性

JS中,函数可以看成一种值,可以赋值给变量、对象,甚至函数的参数或返回值等。

5 函数名的提升

JavaScript 引擎将函数名视同变量名,所以采用function命令声明函数时,整个函数会像变量声明一样,被提升到代码头部。

1)采用function命令声明函数时,整个函数会像变量声明一样,被提升到代码头部

2)如果采用赋值语句定义函数,JavaScript 就会报错。

3)如果同时采用function命令和赋值语句声明同一个函数,最后总是采用赋值语句的定义

"use strict";
// 1 采用function命令声明函数时,整个函数会像变量声明一样,被提升到代码头部
fun();  //返回函数被提升了
function fun() {
   
console.log("被提升了");  //
}

//2 如果采用赋值语句定义函数,JavaScript 就会报错。
fun1();   //报错,fun1 is not a function。原因是变量fun1只是被提升,并没有被赋值
var fun1 = function () {
   
console.log("有被提升");
};


// 3 如果同时采用function命令和赋值语句声明同一个函数,最后总是采用赋值语句的定义
function myFun() {
   
console.log("我是使用Function");
}

var myFun = function () {
   
console.log("我是函赋值给变");
};
myFun();
//我是函数表达式赋值给变量

 

2.7.2 函数的属性和方法

1 name属性

"use strict";
//1 函数的name属性返回函数的名字。
function fun() {
   
console.log("11");
}

console.log(fun.name);  //fun

//2
如果是通过变量赋值定义的匿名函数,那么name属性返回变量名。
var myFun = function(){
   
//2种情况
};
console.log(myFun.name);  //myFun

//3
如果是通过变量赋值定义的具名函数,那么name属性返回function关键字之后的函数名。
var myFun1 =function myName() {
   
//函数体
};
console.log(myFun1.name);  //myName

2 length属性

函数的length属性返回函数预期传入的参数个数,即函数定义之中的参数个数。

3 toString()函数

1)返回一个字符串,内容是函数的源码。

2)函数内部的注释也可以返回。利用这个特性可以实现多行字符串

"use strict";
//利用函数的toString()方法能返回函数的内部注释,返回多行字符串
function myFun() {
   
/*
   
我是
     多行
     字符串
    */
}
function mulString(fun) {
   
var arr = fun.toString().split("\n");
   
return arr.slice(2, arr.length - 2).join("\n");
}

console.log(mulString(myFun));   //记住这里的myFun后面不能加括号,否则会报错
/*输出如下:
    我是
     多行
     字符串
*/

 

2.7.3 函数作用域

1 作用域

1)定义:作用域指的是变量存在的范围

2)分类

全局作用域(ES5:变量在整个程序一直存在,所有地方可以读取。全局作用域中的变量称谓全局变量

函数作用域(ES5:变量只在函数内部。局部作用域中的变量称为局部变量,函数内部定义的变量,会在该作用域内覆盖同名全局变量。

块级作用域(ES6中):暂时不学习

//"use strict";
var a = 2//全局变量
function f() {
   
var a = 1//局部变量,在函数内部会覆盖掉全局变量a
   
var b= 2;   //局部变量
    c = 3;   //严格模式下报错。非严格模式下,在函数内部不使用var声明的变量是全局变量,函数外面也能访问
    console.log(a);
}

console.log(c); //这里报错,因为在函数内部定义的全局变量,只有在调用函数一次之后,变量才会有定义
f();  //1
console.log(b);  //报错,无法调用函数内部的局部变量
console.log(c); //3

 

2 函数内部变量的提升

var命令声明的变量,不管在什么位置,变量声明都会被提升到函数体的头部。

3 函数本身的作用域

函数本身也是一个值,也有自己的作用域。它的作用域与变量一样,就是其声明时所在的作用域,与其运行时所在的作用域无关。

"use strict";
//1 函数执行时所在的作用域,是定义时的作用域,而不是调用时所在的作用域。
var a = 1;
var x = function () {
   
console.log(a);
};

function f() {
   
var a = 2;
    x();  
//其作用域是全局作用域,而不是f()函数的作用域
}
f();
// 1

//2
如果函数A调用函数B,却没考虑到函数B不会引用函数A的内部变量。
var myF = function () {
   
console.log(b);
};

function myY(f) {
   
var b = 2;
    f(); 
//
}
myY(myF);
//报错:b is not defined。因为传入参数myF之后,myF函数的作用域是全局函
          //全局作用域,不会调用myY()函数内部的变量

//3 函数内部声明的函数,作用域绑定在函数内部,即闭包

 

2.7.4 参数

1 调用一个函数,有时需要提供参数

2 参数的省略

JavaScript 允许省略参数,省略的参数的值就变为undefined。

没有办法只省略靠前的参数,而保留靠后的参数。如果一定要省略靠前的参数,只有显式传入undefined。

3 传递方式

1)基本数据类型的值(数值、字符串、布尔值):传值传递(在函数体内修改参数值,不会影响到函数外部。)

2)复杂数据类型:传址传递(传入函数的原始值的地址,因此在函数内部修改参数,将会影响到原始值。)

注意,如果函数内部修改的,不是参数对象的某个属性,而是替换掉(注意对象的属性项数需要一致)整个参数,这时不会影响到原始值。

4 同名参数

如果有同名的参数,则取最后出现的那个值。

//"use strict";
function f(a, a) {
   
console.log(a);
}

f(
1); // undefined。调用函数f的时候,没有提供第二个参数,a的取值就变成了undefined。严格模式下报错

 

5 arguments对象

1)arguments对象包含了函数运行时的所有参数,这个对象只有在函数体内部,才可以使用。

2)修改

正常模式:arguments对象与函数参数具有联动关系。

严格模式:arguments对象与函数参数不具有联动关系。也就是说,修改arguments对象不会影响到实际的函数参数。

3)length属性

判断函数调用时到底带几个参数。

4)与数组的关系:类似数组,实际为对象

转换成数组方法:slice方法以及逐一填入新数组(常用for循环数组的push方法)

5)callee属性(严格模式禁用、不建议使用)

返回它所对应的原函数。

2.7.5  闭包

1 JavaScript 有两种作用域:全局作用域和函数作用域。函数内部可以直接读取全局变量。但是,函数外部无法读取函数内部声明的变量。

2 作用域链

函数f2就在函数f1内部,这时f1内部的所有局部变量,对f2都是可见的。但是反过来就不行,f2内部的局部变量,对f1就是不可见的。这就是 JavaScript 语言特有的"链式作用域"结构(chain scope),子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。

"use strict";
function f1() {
   
var n = "JavaScript";
   
function f2() {
       
console.log(n);
    }
   
return f2;
}

3闭包简单理解成“定义在一个函数内部的函数”。闭包最大的特点,就是它可以“记住”诞生的环境,将函数内部和函数外部连接起来的一座桥梁。

4用处1:一个是可以读取函数内部的变量;另一个就是让这些变量始终保持在内存中,即闭包可以使得它诞生环境一直存在。

"use strict";
function createIncrementor(start) {
   
return function () {
       
return start++;
    };
}

var inc = createIncrementor(5);  //记住这里需要将函数的调用赋值给一个变量,如果不赋值给变量,多次调用只是简单的调用而已
//start是函数createIncrementor的内部变量。通过闭包,start的状态被保留了,每一次调用都是在上一次调用的基础上进行计算。从中可以看到,闭包inc使得函数createIncrementor的内部环境,一直存在。所以,闭包可以看作是函数内部作用域的一个接口。
console.log(inc()); // 5
console.log(inc()); // 6
console.log(inc()); // 7

用处2:是封装对象的私有属性和私有方法。。

"use strict";
function Person(name) {
   
var _age;
   
function setAge(n) {
       
_age = n;
    }
   
function getAge() {
       
return _age;
    }
   
return {
       
name: name,
       
getAge: getAge,
       
setAge: setAge
    };
}

var p1 = Person('');
p1.setAge(25);
console.log(p1.getAge()); // 25
//
函数Person的内部变量_age,通过闭包getAgesetAge,变成了返回对象p1的私有变量。

5 注意,闭包消耗内存大,滥用闭包,会造成网页性能问题。

2.7.6 立即调用函数表达式

1 定义以及格式

函数定义时立即使用

(function(){ /* code */ }());
// 或者
(function(){ /* code */ })();

 

2 扩展体

var i = function(){ return 10; }();
true && function(){ /* code */ }();
0, function(){ /* code */ }();

!function () { /* code */ }();

 

3.目的以及通用格式

一是不必为函数命名,避免了污染全局变量;二是 IIFE 内部形成了一个单独的作用域,可以封装一些外部无法读取的私有变量。

// 写法一
var tmp = newData;
processData(
tmp);
storeData(
tmp);

// 写法二(推荐使用)
(function () {
   
var tmp = newData;
    processData(
tmp);
    storeData(
tmp);
}());

 

2.7.7  eval命令(不推荐使用,省略。。。)

1)作用:接受一个字符串作为参数,并将这个字符串当作语句执行。

2.8 数组

1 定义

1)数组(array)是按次序排列的一组值。每个值的位置都有编号(从0开始),整个数组用方括号表示。

2)除了在定义时赋值,数组也可以先定义后赋值。

3)任何类型的数据,都可以放入数组。

4)多维数组

2 本质:对象

1)typeof运算符会返回数组的类型是object。

2)特点:键名是按次序排列的一组整数(0,1,2...)

3)数组的键名其实也是字符串。之所以可以用数值读取(用方括号结构读取),是因为非字符串的键名会被转为字符串。(读取和赋值时均成立)

3 length属性:返回数组的成员数量

1length具有动态性、可写性(可以人为设置大小)

2等于键名中的最大整数加上1

3)由于数组本质上是一种对象,所以可以为数组添加属性,但是这不影响length属性的值。

"use strict";
//以下数组的键分别设为字符串和小数,结果都不影响length属性。
// 因为,length属性的值就是等于最大的数字键加1,而这个数组没有整数键,所以length属性保持为0
var arr = ["JavaScript"];
console.log(arr.length);  //1
arr['p'] = 'abc';
console.log(arr.length);  //1
arr[2.1] = 'abc';
console.log(arr.length);  //1

//
如果数组的键名是添加超出范围的数值,该键名会自动转为字符串。
arr[-1] = 'a';
arr[Math.pow(2, 32)] = 'b';
console.log(arr.length);  //1
console.log(arr[-1]);  //a
console.log(arr[4294967296]);  //b。取键值时,数字键名会默认转为字符串。

 

4 in运算符

1)作用

检查某个键名是否存在

2)注意:

如果数组的某个位置是空位,in运算符返回false。

"use strict";
var arr = ["a", "b", "c"];
console.log(arr.length);  //3。数组的长度为3
console.log(Object.keys(arr));  //[ '0', '1', '2' ]。返回的是数组中存在的键名

console.log(2 in arr);  //true
console.log("2" in arr);  //true
console.log(9 in arr);  //false。数组中的键名没有“9”,返回false

arr[8] = "h";   //这样设置之后,数组中多了一个名为“8”的键名,长度为9
console.log(arr.length);  //9。数组的长度为9,数组的length属性为键名中最大正数加1
console.log(Object.keys(arr));  //[ '0', '1', '2', '8' ]
console.log("8" in arr);  //true
console.log(6 in arr);  //false。数组中的键名没有“6”,返回false

 

5 数组的遍历

1)for...in不仅会遍历数组所有的数字键,还会遍历非数字键。(不推荐)

2)for循环和while循环,果使用的是数组的length属性,则只会访问数字键。

"use strict";
var arr = [1, 2, 3, 4];
arr.foo = "";   //数组可以动态添加属性,键名为字符串
arr["jio"] = "Java";

//使用for ...in循环,会遍历数字键和非数字键
for (key in arr){
   
console.log(key, arr[key]);
}

/*
输出为:
0 1
1 2
2 3
3 4
foo
非数字键
jio Java
*/

/*

使用for或者while循环遍历数组,如果使用的是数组的length属性,则只会访问数字键。
*/
for(var i = 0; i < arr.length; i++){
   
console.log(arr[i]);
}


var j = 0;
while (j < arr.length){
   
console.log(arr[j]);
   
j++;
}

/*
for
while循环中输出均为
1
2
3
4
*/

 

3)forEach()访问数组(Array标准库中细说)

6 数组的空位

1)当数组的某个位置是空元素,即两个逗号之间没有任何值,我们称该数组存在空位(hole)。

2)特性

A 空位是可读取的,读取时返回undefined;

B 使用delete命令删除一个数组成员,会形成空位,并且不会影响length属性;

"use strict";
var arr = [1, , 2, 3];
delete arr[3];
console.log(arr.length);  //4。使用delete命令不会影响length属性
console.log(arr);   //[ 1, <1 empty item>, 2, <1 empty item> ]

 

C 数组的某个位置是空位(没有元素),与某个位置是undefined(有元素,只是值为undefined而已),是不一样的。如果是空位,使用数组的forEach方法、for...in结构、以及Object.keys方法进行遍历,空位都会被跳过。如果某个位置是undefined,遍历的时候就不会被跳过。

7 类似数组的对象

1)定义

如果一个对象的所有键名都是正整数或零并且有length属性,那么这个对象就很像数组,语法上称为“类似数组的对象”(array-like object)。

2)特征

“类似数组的对象”的根本特征,就是具有length属性。但是length属性不是动态值,不会随着成员的变化而变化。

并且,不具有数组具有的方法

3)典型代表

arguments对象,以及大多数 DOM 元素集,还有字符串。

4)使用数组的slice方法转换成真正的数组(。。。)

var arr = Array.prototype.slice.call(arrayLike);

 

55)以通过call()把数组的方法放到对象上面,然后使用数组的方法(。。。)

function print(value, index) {
   
console.log(index + ' : ' + value);
}


Array.prototype.forEach.call(arrayLike, print); // arrayLike类似一个数组

 

2.9 typeof操作符

1 typeof操作符意义:检测给定变量的数据类型。

2 JS中有三种方法确一个值是什么类型

1)typeof运算符

2)instanceof运算符(后面介绍)

3)Object.prototype.toString()方法(后面介绍)

3 typeof操作符返回值:undefined、boolean、number、string、function(函数时返回)、object(这个值是对象、null和数组时)

"use strict"//表示严格模式
var a = undefined;
var b = true;
var c = 10;
var d = "str";
function myFunction(){
   
return 1;
}

var e = null;
var g = [1, 2, 3];
console.log(typeof(a));    //undefined
console.log(typeof(b));    //boolean
console.log(typeof(c));   //number
console.log(typeof(d));    //string
console.log(typeof(myFunction));  //function
console.log(typeof(myFunction()));  //number 请注意这里,注意,为何自己思考。
console.log(typeof(e));   //  object
console.log(typeof(g));   //  object

4 注意:在Safari5以及之前版本、Chrome7以及之前版本,正则表达式调用typeof会返回“function”,其他为“object”

2.10 思维导图(本地的时候的图片好好的,上传就这样了。。。。看我打印出来的,好看多了。。。。。)

 

第2章 JavaScript数据类型--2(复杂数据类型,即对象、函数、数组;typeof和思维导图)读书笔记

第2章 JavaScript数据类型--2(复杂数据类型,即对象、函数、数组;typeof和思维导图)读书笔记

第2章 JavaScript数据类型--2(复杂数据类型,即对象、函数、数组;typeof和思维导图)读书笔记

如有错,请指出,如有侵权,请联系我,谢谢!

参考资料

1 JavaScript 教程https://wangdoc.com/javascript/basic/grammar.html

2 《JavaScript高级程序设计 第3版》