# 2、数据类型概述(二)
# 1、对象
# 一、 概述
# 1.1 生成方法
对象是 JavaScript 语言的核心概念,也是最重要的数据类型。
对象是一组键值对(key-value)的集合,是一种无序的复合数据集合。 键名+键值
var obj = {
foo:'hello',
bar:'World'
}
var obj = new Object({foo:'hello',bar:'World'})
# 1.2 键名
- 对象的所有键名都是字符串(ES6 又引入了 Symbol 值也可以作为键名),所以加不加引号都可以。上面的代码也可以写成下面这样。
var obj = {
undefined:"und",
null:"nu",
123:'123123',
true:'true',
"m":"mmm",
1.2:'1.2',
"1p":"pp"
}
以上都是合法的,简单数据类型会转化成字符串
- 嵌套的属性可以进行链式调用
var a = {
b:{
c:{foo:'hello'}
}
}
a.b.c.foo // 'hello'
- 属性可以动态创建,不必在对象声明时就指定。
var aa = {}
aa.foo = 'hello'
a.foo. // 'hello'
- 键值可以是任意数据类型,如果属性值是一个函数,通常把这个属性称为‘方法’,可以像函数那样调用
var a = {
p: function(x) {
return x * 2;
}
};
a.p(1); // 2
# 1.3 对象的引用
如果不同的变量名指向同一个对象,那么它们都是这个对象的引用,也就是说指向同一个内存地址。修改其中一个变量,会影响到其他所有变量。
var obj1 = { foo: "hello" };
var obj2 = obj1;
obj2.foo = "hi";
obj1; // {foo:'hi'}
如果把第一个对象赋值为简单数据类型(开辟了新的空间),不会对第二个产生影响
var obj1 = { f00: "hello" };
var obj2 = obj1;
obj2.o = "hi";
obj1 = true || 1;
obj2; // {foo:'hello',o:'hi'}
var y = 1;
var x = y;
y = 2;
x; // 1
# 二、属性的操作
# 2.1 属性的读取
两种方式,一种是使用点运算符,还有一种是使用方括号运算符。
var obj = {
p: "Hello World"
};
obj.p; // 'Hello World'
obj["p"]; // 'Hello World'
方括号内部可以用表达式
obj["hello" + " world"];
obj[3 + 3];
数值可以不加引号,会自动转化成字符串形式
var obj = {
123:'hello world'
};
obj.123. // 报错
obj[123] // 'hello world'
# 2.2 属性的赋值
点运算符和方括号运算符,不仅可以用来读取值,还可以用来赋值。JavaScript 允许属性的“后绑定”,可以在任意时刻新增属性,不用提前定义
var obj = {p:1}; => var obj={}; obj.p = 1;
obj.foo = 'Hello';
obj['bar'] = 'World'
# 2.3 属性的查看
查看一个对象本身的所有属性,可以使用Object.keys
方法。
var obj = {
key1 = 1,
key2 = 2
};
Object.keys(obj);
// ['key1','key2']
# 2.4 属性的删除: delete 命令
delete
命令用于删除对象的属性,删除成功后返回true
。
var obj = {
p: 1
}
Object.keys(obj) // ['p']
detete obj.p. // true
Object.keys(obj) // []
对象删除已有或没有的 key 都会返回 true,所以不能根据 delete 判断是不是有这个属性,可以根据 obj.hasOwnProperty("key")判断
但是如果属性被保护,不能删除则返回 false
delete 不能删除继承属性:如 obj 的 toString 属性
var obj = Object.defineProperty({}, "p", {
value: 123,
configurable: false
});
obj.p; // 123
delete obj.p; // false
obj.hasOwnProperty("p"); // true
# 2.5 判断属性是否存在:in 运算符 和 hasOwnProperty
in
运算符用于检查对象是否包含某个属性(注意,检查的是键名,不是键值),如果包含就返回true
,否则返回false
。它的左边是一个字符串,表示属性名,右边是一个对象。
var obj = { p: 1 };
"p" in obj; // true
"toString" in obj; // true
in 运算符只能判断属性不是不是自身的,不能判断是不是继承而来,
hasOwnProperty 可以判断
var obj = {};
if ("toString" in obj) {
console.log(obj.hasOwnProperty("toString")); // false
}
# 2.6 属性的遍历: for... in 循环
for...in
循环用来遍历一个对象的全部属性。
var obj = { a: 1, b: 2, c: 3 };
for (var key in obj) {
console.log("键名" + key);
console.log("键值" + obj[key]);
}
- 它遍历的是对象所有可遍历(enumerable)的属性,会跳过不可遍历的属性。
- 它不仅遍历对象自身的属性,还遍历继承的属性
因为 for in 会遍历继承而来的属性,多数不需要,所以最好结合 hasOwnProperty 判断过滤一下
var person = { name: "老张" };
for (var key in person) {
if (person.hasOwnProperty(key)) {
console.log(key);
}
}
// name
# 三、with 语句
格式
with (对象) {
语句;
}
作用:操作同一对象的多个属性时,书写方便。
var obj = {
p1 : 1,
p2 : 2
}
with(obj){ => obj.p1 = 3; obj.p2 =4
p1 = 3,
p2 = 4
}
如果对象中没有的值在 with 中修改会创建全局变量,这是with
语句的一个很大的弊病,就是绑定对象不明确。 所以尽量不用,或用临时变量替代。
2-26
# 2、函数
# 一、概述
# 1.1 函数的声明
JavaScript 有三种声明函数的方法。
(1)function 命令声明函数
```js
function print(s){ console.log(s) } print();
(2)函数表达式, 匿名函数变量赋值的写法
var print = function(s){ console.log(s) } print()
如果带有函数名,只能在内部访问,外部访问不到
```js
var f = function f() {};
这种写法目的:1、方便除错 2、方便函数内部调用自身
(3)Function 构造函数
var add = new Function(
'x',
'y',
'return x+y'
)
=>
function add(x,y){
return x+y
}
Function 构造函数除了最后一个是函数体,其他都是参数
var foo = new Function(
'return "hello world";'
);
=>
var foo = Function(
'return "hello world";'
);
这种方式非常不直观,几乎无人用
- 函数如果重复声明,后一次的函数声明会覆盖前面一次的
# 1.2 圆括号运算符,return 语句和递归
圆括号运算符中传入函数的参数
return 后面跟表达式,后面语句不会继续执行
没有 return,不反回,或者说返回 undefined
函数可以调用自身,也就是递归函数
funciton fib (num){
if(num ===0)return 0;
if(num ===1) return 1;
return fib(num-2)+fib(num -1)
fib(4) + fib(5)
fib(2)+fib(3)+fib(3)+fib(4)
}
fib(6) // 8
# 1.3 第一等公民
JavaScript 语言将函数看作一种值,与其他值地位相同,可以用值的地方就可以用函数,比如可以赋给变量,可以作为参数传入,可以作为对象的属性,可以作为函数结果返回。因为函数与其他数据类型地位平等,所以 JavaScript 语言又被称为函数的第一等公民。
function add(x, y) {
return x + y;
}
// var operator = add;
// 将函数作为参数和返回值
function a(op) {
return op;
}
a(add)(1, 1);
# 1.4 函数名的提升
JavaScript 引擎将函数名视为同变量名,所以 function 声明函数时,整个函数会像变量声明一样,被提升到代码头部
f(); // 不报错
function f() {}
如果是赋值语句
f() // TypeError: undefined is not a function
var f = function(){}
=>
var f;
f()
f = function(){}
如果同时采用 function 命令和赋值语句声明同一个函数,最后总是采用赋值语句的定义。
# 二、函数的属性和方法
# 2.1 name 属性
函数的name
属性返回函数的名字
function f1(){}
f1.name // 'f1'
var f2 = function(){}
f2.name. // 'f2'
var f3 = function myName(){}
f3.name. // myName
应用
var test = function myFun(){}
function g(f){f.name}
g(test) // myFun
# 2.2 length 属性
function a(x,y){}
a.length. // 2
# 2.3 toString()
函数的 toString 方法返回一个字符串,内容就是函数的源码
如果是原生的函数,返回function (){[natice code]}
函数内部注释也可以返回,可以利用此返回多行文本
function f() {
/*
这是一个多行文本
*/
}
f.toString()
.split("\n")
.slice(2, -2)
.join("\n");
# 三、 函数作用域
# 3.1 定义
作用域(scope)指变量的存在范围,ES5 中,JS 有两种作用域,一种是全局作用域,一种是函数作用域。ES6 又新增了块级作用域。
对于顶层函数来说,外部声明的变量是全局变量,内部的是局部变量,只能在内部访问。
var a = 1;
function test() {
var a = 3;
console.log(a); // a = 3 该作用域内局部变量会覆盖全局变量
}
console.log(a); // a=1
test();
由于 var 只有函数作用域,所以除了函数外,任何区块声明都是全局变量
if (true) {
var x = 5;
}
console.log(x); // 5
# 3.2 函数内部的变量提升
与全局作用域一样,函数作用域内部也会产生“变量提升”现象。var
命令声明的变量,不管在什么位置,变量声明都会被提升到函数体的头部。
function foo(x){
if(x>100){
var tmp = x -100;
}
}
=>
function foo(x){
var tmp;
console.log(tmp) // 不报错 undefined
if(x>100){
tmp = x -100
}
}
# 3.3 函数本身的作用域
函数本身也是一个值,也有自己的作用域,他的作用域之和声明时所在的作用域有关,与运行时所在的作用域无关。
var a = 1;
var x = function test() {
console.log(a); // 1 // 全局没有定义a时,为undefined
};
function f(x) {
var a = 2;
x();
}
f();
函数体内部声明的函数,作用域绑定函数体内部,这是这种机制,构成了闭包
function a() {
var foo = 1;
return function b() {
console.log(foo); // 1
};
}
var foo = 2;
a()(); // 1
# 四、参数
# 4.1 概述
函数运行的时候,有时需要提供外部数据,不同的外部数据会得到不同的结果,这种外部数据就叫参数。
# 4.2 参数的省略
函数参数不是必需的,JavaScript 允许省略参数。
function f(a, b) {
return a;
}
f(1, 2, 3); // 1
f(1); // 1
f(); // undefined 可以通过es6给参数设置默认值
f.length; // 2
# 4.3 传递方式
原始类型的值(布尔、数值、字符串),参数传递到函数,函数体内修改不会影响原始值,
复合类型的值(数值、对象、其他函数),传递进来的值修改后会影响原始值。如不想修改原始值,需
要克隆一份进行修改
# 4.4 同名参数
函数传入两个参数同名,取最后一个的值
function f(a, a) {
// 默认是取第二个参数,但是第二个是undefined 所以打印undefined,但是如果想取第一个的参数可以用arguments取参
console.log(arguments[0]);
}
f(1); // 1
# 4.5 arguments 对象
函数允许有不通数目的参数,需要一种机制读取所有的参数,这就是 arguments 对象。
这个对象包含了函数运行时所有的参数。
const f = functin(one){
console.log(arguments[0])
console.log(arguments[1])
console.log(arguments[2])
}
正常模式下 arguments 对象可以在运行时修改值,但是严格模式下不可以
arguments.length 判断运行时,传入的参数个数
虽然 arguments 像数组但不是数组,slice 和 forEach 等都不可用
可以先转化成数组,转化成数组的方法是
var args = Array.prototyoe.slice.call(arguments);
// 或者
var args = [];
for (var i = 0; i < arguments.lenght; i++) {
args.push(arguments[i]);
}
# 五、函数其他知识点
# 5.1 闭包
闭包(closure) 是JavaScript语言一个难点,也是特色
,很多高级应用都要依靠闭包实现。Javascript 有两种作用域,
全局作用域和函数作用域
,函数内部可以读取全局变量,但是函数外部却无法读取函数内部声明的变量。所以想要获取函数内部的变量,需要在函数内部再定义一个函数,读取到值后,在将这个函数和它读取的值一起返回。表现为
函数嵌套函数
的形式。JavaScript 语言特有的
"链式作用域"结构(chain scope)
,子对象会一级一级地向上寻找所有父对象的变量。父对象的所有变量,对子对象都是可见的,反之则不成立。用处:(1)读取函数内部变量 (2)让变量保持在内存中,不被销毁
缺点:易造成内存泄露,外层函数每次运行都护生成一个新的闭包,这个闭包又会保留外层函数的内部变量,所以内存消耗很大,因此不能滥用闭包,负责容易造成网页性能问题。
解决:将用完的函数或者变量置为 null。
function f1() {
var a = 1;
return function() {
return a;
};
}
f1()(); // 1
// 没执行一次加一
function createIncrementor(start) {
return function() {
return start++;
};
}
var inc = createIncrementor(5);
inc(); // 5
inc(); // 6
inc(); // 7
- 利用闭包实现点赞效果
<body>
<button></button>
<button></button>
<button></button>
</body>
<script>
const buttons = document.getElementsByTagName('button')
const nums = [20,31,0]
for(var i=0;i<buttons.length;i++){
buttons[i].innerHTML = `点赞${nums[i]}`
buttons[i].onclick=fi(nums[i])
// 显示点击的按钮索引
// buttons[i].onclick=(function(i){
// return function(){
// console.log(i,'iii')
// }
// })(i)
}
function fi(count){
return function(){
this.innerHTML = `点赞${++count}`
}
}
</script>
- 实现一个 getter、setter 方法
var getValue, setValue;
(function() {
var secret = 1;
getValue = function() {
return secret;
};
setValue = function(value) {
secret = value;
};
})();
alert(getValue()); // 1
setValue(4);
alert(getValue()); // 4
- 封装私有化的属性和方法
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);
p1.getAge(); // 25
# 5.2 立即调用的函数表达式(IIFE)
在 JavaScript 中,圆括号是一种运算符,跟在函数后面,表示调用该函数,比如print()
就表示调用 print 函数。
function
这个关键字既可以作为语句,也可以作为表达式。
// 语句
function f() {}
// 表达式
var f = function f() {};
JavaScript 引擎规定,如果 function 关键字在行首,就认为是函数语句,只要不让 function 出现在行首,即认为是一个表达式
所以一般用立即调用的函数表达式,注意表达式后面加 ';'
好处:(1)不必为函数命名,避免污染全局变量 (2)IIFE 内部形成单独的作用域,可以封装外部无法读取的私有变量
(function() {
var tmp = new Data();
storeData(tmp);
})();
# 六、eval 命令
# 6.1 基本用法
eval命令接受一个字符串作为参数,并将这个字符串当作语句执行。
eval("var a = 1;");
a; // 1
因为 eval 命令没有自己的作用域,有可能改写外部变量,具有安全风险,另外不利于 js 引擎优化执行速度,所以一般不推荐使用,常见的场合是解析 JSON 数据的字符串,但是正确做法是使用原声的 JSON.parse 方法
# 6.2 eval 的别名调用
只要不是eval( )
调用,全是别名调用,javascript 规定,别名调用,作用域为全局
var a = 1;
function f() {
var a = 2;
var e = eval;
e("console.log(a)");
}
f(); // 1
# 3、数组
# 一、 定义
数组是一次序排列的一组值。每个值的位置都有编号(从 0 开始),整个数组用方括号表示。
var arr = ["a", "b", "c"];
arr[3] = "d";
arr; // ['a','b','c','d'];
任何类型的数据都可以放入数组;
var arr = [
{ a: 1 },
[1, 2, 3],
function() {
return true;
}
];
arr[0]; // Object {a:1}
arr[1]; // [1,2,3]
arr[2]; // function(){return true}
数组的元素也是元素,称为多维数组
var a = [
[1, 2],
[3, 4]
];
a[0][1]; // 2
a[1][0]; // 3
# 二、 数组本质
本质上,数组属于特殊的对象,typeof 运算符会返回数组的类型是
object
它的键名是依次排列的整数,Object.keys(arr) 可以看出
取值和赋值可以用方括号结构 arr['1'] 或 arr[1] 数值会转化成字符串键值
# 三、 length 属性
- 数组的 length,返回数组的成员数量
[1,2,3].length. // 3
- 数组的 length 始终比最大的键值大于 1
const arr = [1, 2];
arr[999] = 9;
arr; // (1000) [1, 2, empty × 997, 9]
arr.length; // 1000
- 设置 length 长度,可以改变数组的长度
// length小于数组长度
const arr = [1,2,3,4]
arr.length = 1
arr // [1,2]
arr.length = 0
arr = []
// length大于数组长度
arr.length = 99
arr = [1,2]
arr. // (99) [1, 2, empty × 97]
// 当取值为空时会返回undefined
const arr = [1,2]
arr[2] = 3 // undefined
- 由于数组本质上是一种对象,数组添加属性后,length 不变
var a = [];
a.lenght; // 0
a["p"] = "123";
a; // [p:"123"]
a.length; // 0
# 四、in 运算符
检查某个键名是否存在的运算符in
,对象和数组都是用
var arr = ["a", "b", "c"];
1 in arr; // true
"2" in arr; // true
5 in arr; // false 空值也会返回false
# 五、 for...in 循环 和数组遍历
for...in
既可以遍历对象也可以遍历数组,但是不推荐遍历数组
var a = [1, 2, 3];
for (var i in a) {
console.log(a);
}
a["foo"] = true;
// 1
// 2
// 3
// foo
for...in
在遍历数组的时候不仅遍历数字键,也会遍历非整数键 foo,所以不提交用for...in
遍历数组
- 数组的便利可以考虑使用 for 循环或 while 循环
var arr = [1, 2, 3, 4, 5, 6, 7];
// for 循环
for (var i = 0; i < arr.length; i++) {
console.log(arr[i]);
}
// while循环
var i = 0;
while (i < arr.length) {
console.log(arr[i]);
i++;
}
// while逆向遍历
var l = arr.length;
while (l--) {
console.log(arr[l]);
}
// forEach 方法遍历
var color = ["red", "green", "blue"];
color.forEach(function(color) {
console.log(color);
});
// red
// green
// blue
# 六、数组的空位
数组的空位指的是两个逗号之间没有任何值,最后有逗号不会产生空位,空位也占位置,算到 length 中
空位读取是 undefined
delete 删除会形成空位,并且不会影响 length 长度
var arr1 = [,,2,]
arr1.length // 3
var arr2 = [,,2,,]
arr2.length // 4
arr2[1] // undefined
var arr3 = [1,2,3]
delete arr3[1]
arr3 // [1,,3]
arr3.length // 3
arr3[1] // undefined
遍历的情况下,无论 for...in 、Object.keys 还是 forEach 遍历,空位都会被跳过
var a = [, , ,];
a.forEach(function(x, i) {
console.log(i + ". " + x);
});
// 不产生任何输出
for (var i in a) {
console.log(i);
}
// 不产生任何输出
Object.keys(a);
// []
但是 undefined 不会
var b = [undefined, undefined];
a.forEach(function(x, i) {
console.log(i + ". " + x);
});
// 0. undefined
// 1. undefined
// 2. undefined
for (var i in a) {
console.log(i);
}
// 0
// 1
// 2
Object.keys(a);
// ['0', '1', '2']
# 七、类似数组的对象
如果有一个对象所有的键名都为正整数或 0,并且有 length 属性,那么就把这个对象称为"类似数组的对象(array-like object)"
var obj = {
0: "a",
1: "b",
2: "c",
length: 3
};
obj[0]; // 'a'
obj[1]; // 'b'
obj[2]; // 'c'
length; // 3
obj.push("d"); // TypeError:obj.push is not a function
以上,对象 obj 就是一个类似数组的对象,但是,类似数组的对象并不是数组,也不具有数组特有的方法,对象 obj 不具有数组的 push 方法。
‘类似数组的对象‘的根本特征是,具有 length 属性,但是这个 length 不是动态变化的,不会随成员的变化而变化。说明不是真正的数组
var obj = {
length: 0
};
obj[3] = "d";
obj.lenght; // 0
典型的“类似数组的对象”是函数的arguments
对象,大多数 DOM 元素集,还有字符串。
// arguments对象
function args(){
return arguments
}
const likeArr = args(1,2,3)
likeArr.length // 3
likeArr[0] //1
likeArr instanceof Array // false
//DOM元素集
var elts = document.getElementsByTagName('h3');
elts.lenth // 3
elts instanceof elts // false
// 字符串string
'abc'[1] // 'b'
'abc'.length // 3
'abc' instanceof Array. // false
以上三个例子很像数组,但是不是真正的数组,但是slice
可以将他们变成真正的数组。
var likeArr = "weopp";
var arr = Array.prototype.slice.call(likeArr);
// 等价于 Array.from(likeArr)
arr; // ['w', 'e', 'p' , 'p']
arrayLike
代表一个类似数组的对象,本来是不可以使用数组的forEach()
方法的,但是通过call()
,可以把forEach()
嫁接到arrayLike
上面调用
// 1、把数组的forEach的方法嫁接到类数组对象上去使用,通过call 执行方法
var arrayLike = "sa"
Array.prototype.forEach.call(arrayLike, (value, index)=>{
console.log(index + ' : ' + value);
});
// 0 : s
// 1 : a
// 2、等同于 for 循环
function logArgs() {
for (var i = 0; i < arguments.length; i++) {
console.log(i + '. ' + arguments[i]);
}
}
//3、通过slice先转成数组,在遍历 等同于 Array.from()
Array.prototype.slice.call('123').forEach(...)
Array.from('123').forEach(...)
// 4、es6中,解构[...s]可以转化为数组
2020-02-28