对象
对象的创建
-
造函数创建法
var obj=new Object();
-
字面量创建法
var obj = { // key:value; name: hm, // 字符型key "name": hm, // 变量型key [a]: 12, };- 点语法拒绝使用非字符类型属性
- 在对象中 key 除了字符类型以外只能支持 symbol
- 所有对象在转换成字符串以后都会变成[Object Object](第一个 Object 说明是一个对象 第二个 Object 说明是对象类型);
var a = "keys"; var obj = { // key:value; name: "hm", // 字符型key name: "hm", // 变量型key [a]: 12, }; var o = { a: 1, }; var o1 = { b: 2, }; obj[o] = 10; //在添加属性时,对象o被转换为字符串类型[Object Object],所以在obj中会增加[Object Object]:10; console.log(obj[o1]); //10 s在获取对象的属性值时,o1会转换成字符串[Object Object],所以能获取到10 obj[undefined] = 2; obj[null] = 3; var arr = [1, 2, 3]; //数组类型不变 var arr1 = [4, 5, 6]; obj[arr] = 40; console.log(obj[arr]); console.log(obj);
对象的引用存储
-
对象存储在堆中
-
对象的属性之间没有关联关系
-
判断对象是否相等,仅判断地址,不是判断对象是值
var obj = { a: 1 }; var obj1 = obj; //将对象{a:1}的地址赋值给obj1,所以obj和obj1指向同一个对象 console.log(obj === obj); //判断对象是否相等,仅判断地址,而不是判断对象内容 console.log(JSON.stringify(o) === JSON.stringify(o1)); //判断对象内容是否相等,有漏洞,有可能会出错 -
判断两个对象的值是否相等,可以将对象转成 JSON 字符串,但是可能会出错
var str = JSON.stringify(obj); //对象转换为JSON格式的字符串- 当属性自身具有特殊性时,会出错
-
JSON
-
将对象转换成字符串
-
JSON 字符串
'{"a":1,"b":2,"c":"hm","checked":true,"o":{}}'
var str = JSON.stringify(obj); //将对象转换成JSON字符窜 var s = '{"a":1,"b":2,"c":"hm","checked":true,"o":{}}'; var o = JSON.parse(s); //将JSON字符串转换成对象 console.log(o);
-
- 垃圾回收机制
- 在堆中的对象可能被若干个变量引用其地址,如果这个对象在后面的内容中不再使用,我们需要将堆堆中的这个对象清除,否则不会被浏览器自动清除,这样就会造成内存垃圾的产生,当不断产生这种垃圾时,我们就把这种情况叫内存泄漏。
- 如何处理内存泄漏
- 先创建每个对象的管理池,针对每个对象所引用它的变量做统一存储管理,如果需要清理该对象时,将引用它的所有变量设置为 null,当内存上限达到峰值时,系统会通过垃圾回收车将这些堆中无引用的对象全部回收,这就是垃圾回收机制
对象遍历
-
对象的遍历是通过添加属性的先后顺序遍历的
-
广度遍历
- for...in...
for (var prop in obj) { console.log(prop, obj[prop]); } //对象的遍历是通过添加属性的先后顺序遍历的 console.log(obj);- 对象复制
- 浅复制
- 只是第一层的值不会改变,原对象的第二层改变,新对象的第二层也会跟着改变
- 深复制
- 两个对象是完全独立的个体,原对象的改变不会引起新对象的改变
- JSON 实现深复制
var obj2 = JSON.parse(JSON.stringify(obj)); console.log(obj2);- 不能复制方法
- 递归实现深复制
function cloneObj(obj,target){ target=target||{}; for(var prop in obj){ if(typeof obj[prop]==="object" && obj[prop]!==null){ target[prop]={}; cloneObj(obj[prop],target[prop]); }else{ target[prop]=obj[prop]; } } return target; }
- 浅复制
delete 删除对象属性
delete obj.a;
console.log(obj);
- delete 不能删除任何 window 下的属性
函数
函数基础
- 抽象
- 在设计函数时,尽量不要让函数内部与外部有关联,尽量让函数内部是一个独立的个体
-
函数的创建
-
函数是一个对象,存储在堆中,具备引用关系,可以在函数里面增加对象属性
-
普通函数
function fn(arg1,arg2,...){ //函数的语句块 }-
特点
- 普通函数创建,在 script 标签被执行时,就放入堆中,并且在堆中以函数名作为变量引用堆中这个函数地址
- 函数在创建时,就创建了这个函数名的变量,因为它是全局变量,就会被污染覆盖
- 覆盖前仍然可以执行当前函数,覆盖后,函数不能执行
- 如果函数中没有使用 return 关键字,返回 undefined
-
console.dir()将特殊对象以对象形式展现
-
匿名函数创建
var fn = function () { //匿名函数创建,创建好的匿名函数赋值给fn这个变量 };- 变量什么时候定义,这个变量才能在栈中产生,才可以被调用,变量未定义之前执行函数会报错
- 使用场景
bn.onclick = function () { console.log("aa"); }; - 自执行、自调用函数
(function () { console.log("aa"); })();- 会自动执行,以后永远不能再调用- 构造函数创建
var fn = new Function("a","b","consoloe.log(a+b"));- 注意:
- 构造函数创建,每个参数都必须是字符串
- 除了最后一个参数是函数的语句块以外,前面所有的参数都是函数的参数
- 效率最低
- 特点:只要是字符串,就可以创建函数
-
-
函数的参数
-
在函数的()里面所填写的变量
-
使用参数的目的
-
函数如果没有参数,处理时是不能变化的,参数的目的是在不同情况下解决不同问题
-
问题
- js 是一种弱类型语言,不能对参数约束其类型,就会造成因为使用者输入的参数不符合需求而造成代码出错,无法通过白盒测试
- ES5 版本中,js 中参数不能设置初始值不填写参数就是 undefined
-
-
形参
- 函数定义时的参数
- fn.length
- 形参数量
- arguments.callee.length < arguments.length;
- 形参长度小于实参长度
- 函数定义时的参数
-
实参
- 函数执行时填入的参数
- 实参按照形参顺序一一赋值给形参
- 如果实参数量小于形参数量,后面的形参值是 undefined
-
arguments 的使用
-
如果实参数量大于形参时,使用 arguments
-
arguments 只能出现在函数语句块中
-
用于当前函数的参数不固定数量
-
属性
- arguments.length
- 实参长度
- arguments.callee.name
- 当前函数的名字
- arguments.callee
- 当前函数
- arguments.callee.caller
- 调用当前函数的外部函数
- arguments.length
-
- 变量的作用域
-
变量重新定义不赋值不起作用
-
局部变量
-
生命周期
- 函数执行完成后即销毁
-
变量只能在局部中使用
-
被定义再函数内部的变量,使用范围仅在函数内部,并且当前函数执行完成以后,该变量会销毁,下一次函数执行时,会重新定义该变量,并且变量不能保存在函数执行完成后还能使用
-
当全局变量和局部变量同名时,函数优先使用局部变量,此时如果想使用全局变量,需要使用 window.全局变量,该方法在 ES6 中被禁止
- 如果当前函数内没有定义与全局变量相同的局部变量,函数内会直接使用全局变量
-
当前函数内存在于全局变量同名的未赋值的局部变量时,遵照局部变量优先原则
-
当前函数中存在于全局变量同名的局部变量,但函数使用该变量在局部变量定义之前,仍然遵循局部变量优先原则
-
在函数中只要看到使用 var 定义的变量,这个变量就一定是局部变量,不论是在循环里、条件里,都会变成局部变量,而且这个变量被优先
-
在函数中设置了参数,就相当于这个参数就是被定义好的局部变量
-
如果函数内定义的局部变量与参数同名,参数会被新定义的局部变量覆盖,但是如果局部变量未赋值,则不会影响参数值(变量提升)
-
-
全局变量
- 生命周期
- 定义后一直存在
- 在任何地方都可以使用的变量
- 所有的全局变量都是属于 window 的属性
- 生命周期
-
return
-
return 语句会终止函数的执行并返回函数的值,在调用函数的地方可以用变量接收函数的返回值。
-
return 关键字可以返回任何形式的变量或表达式,或者什么都不返回
-
return 可以阻止后续代码的执行,这样可以有效的控制语句在一定的条件下执行,但与 break 不同
-
break 用在条件中,跳出的是条件和循环
-
return 可以跳出函数,仅针对函数使用,没有函数不能使用 return
-
函数在执行时,返回函数中 return 的内容
-
如果没有 return,将返回 undefined
-
函数返回的作用
- 返回局部变量
- 返回参数
- 跳出,切断,不继续执行函数
-
-
函数的运行过程
- 函数执行中其他函数,另一个函数中的内容执行完成后才会继续执行当前函数。当需要并列执行多个时,可以在一个函数中统一调配执行顺序
-
回调函数
- 将一个函数以参数的形式传入到另一个函数中,并且在那个函数执行
function fn1(fn) { fn(); } function fn2() {} fn1(fn2); -
应用场景
-
一般用于处理某件事情需要等待时,设置回调
-
当不需要担心具体后续需要处理的事情时,设置回调
-
setTimeout(超时执行的函数,超时时间,执行函数的参数)
var id = setTimeout( function (n) { console.log(n); clearTimeout(id); //清除时间间隔 }, 2000, 5 ); //异步- 设置一个时间,当时间到达时,执行函数,只执行一次
- 返回一个 id 数字
- 异步执行,一定时间后处理问题
-
clearTimeout(id);
-
清除超时函数
-
setInterval();
var id = setInterval(fn, 2000); var num = 0; function fn() { console.log("aaa"); num++; if (num > 3) clearInterval(id); }- 每隔一定时间就会执行一次函数,会执行多次
- clearInterval(id);
- 清除时间间隔函数
-
- 递归函数
-
自调用函数,在函数体内部直接或间接的自己调用自己,即函数的嵌套调用是函数本身
-
根据内存大小设置递归上限的次数,如果递归次数太多,会堆栈上限溢出
-
每递归一次就会创建一个副本
var i = 0; fn1(); function fn1() { i++; if (i < 3) fn1(); console.log(i); } // 以下是递归过程 function fn1() { i++; if (i < 3) fn2(); console.log(i); } function fn2() { i++; if (i < 3) fn3(); console.log(i); } function fn3() { i++; if (i < 3) fn4(); console.log(i); } //不被执行 function fn4() { i++; if (i < 3) fn4(); console.log(i); } -
函数调用机制
- 任何函数之间不能嵌套定义
- 调用函数与被调用函数之间相互独立(彼此可以调用)
- 发生函数调用时, 被调函数中保护了调用函数的运行环境和返回地址,使调用函数的状态可以在被调用函数
数组
-
数组是对象类型,也是引用类型,引用地址赋值
- 数组在强转或隐式转换为字符串时,都会转换为数组的元素连接字符串值
-
有序列表存储若干个无序的元素
-
将元素放在列表的第几位,从 0 开始计算,这个位置就是下标,即索引值
-
元素被存储在列表中,这个数据就是元素,简称元
-
从列表中获取这个元素的方法,使用数组名[下标]就可以得到这个元素,这种方式叫做下标变量
-
对象和数组的区别
-
数组是紧密型结构,仅用下表存储对应的值,当删除一个元素时,因为紧密结构的关系,就会将后面的元素向前递进,数组是有长度的,数组中可以知道存储了多少元素,因为插入和删除都会影响数组的元素的位置和结构,因此插入和删除都会影响数组的运行效率,时间复杂度较高。数组的存储依靠下标,如果需要查找一个值,就需要遍历数组的每一个元素,已到达找到目标元素,因此时间复杂度是极高的,数组在使用时,因为是紧密型结构,我们可以根据上一个内容找到与其相关联的其他元素,例如我们可以利用数组排序,找一个最小值,还可以迅速找第二位最小值
-
对象是一种松散型结构,属性以键值对存储,当删除一个元素时,对象的其他值不会发生变化,对象没有元素的个数,也就是对象长度,对象中不知道存储了多少个元素,插入和删除都不会影响其他数据,所以时间复杂度极低,通过 key 一一对应存储一个值,所以获取时,只需要根据 key 去取值,所以时间复杂度极低。对于对象来说,因为每个数据都独立存在,不存在关联关系,更不能排序,所以不能因关联关系找到对应的值
-
数组的创建
-
因为 js 是弱类型语言,因此数组较随意,可以任意扩张,不需要限定数组的长度
- 字面量创建
var arr = [1, 2, 3, 4, 5, 6];-
创建空数组
var arr = []; -
在数组中混合存储数据,存储效率极低,通常把同种类型的数据存储在一个数组中
var arr = [1, "a", true, undefined];
-
构造函数创建
var arr = new Array(1, 2, 3, 4);var arr = Array(0);
-
如果构造函数创建数组时,仅有一个参数且这个参数是一个大于等于 0 的正整数,这个数就是这个新数组的长度,并且没有元素。如果这个数是负数或者小数,就会报错。
-
如果是非数值类型,就会将这个元素放在数组的第一位
-
对象创建
var arr = new Object([1, 2, 3, 4]);console.log(typeof arr);//结果是 object
-
区分对象与数组的方法
console.log(arr.constructor === Array);console.log(Array.isArray(arr)); -
仅限 ES6 以后的浏览器支持
console.log(String(arr) !== "[Object Object]"); -
空数组与其他值的比较
console.log([] === []); //f console.log([] == []); //f console.log([] == ""); //t console.log([] == 0); //t console.log([] == null); //f console.log([] == undefined); //f 除 null和undefined以外,undefined与任何值都不相等 console.log(![] == []); //t ![]会转换成false,空数组先转换成空字符串,然后转换成布尔值false
数组的长度
- arr.length
-
长度,数组中元素的个数
-
数组的长度是一个可读可写的属性
-
如果数组的长度修改为比原来的长度小,就会把对应多出来的元素删除
var arr = [1, 2, 3, 4]; console.log(arr); arr.length--; //删除最尾部的一个元素 console.log(arr); arr.length = 0; //清空数组 console.log(arr); arr.length++; //在数组尾部增加一个空元素 console.log(arr); arr.length--;//删除最尾部的一个元素 arr.length=0;//清空数组中所有元素 arr.length++;//在数组尾部添加一个空元素 //数组的长度比数组的最大下标大1 arr[arr.length]=5;//在数组的尾部增加一个元素5 //如果数组下标大于数组的长度时,就会在对应的下标位增加元素,中间增加空元素 -
数组使用for循环遍历会将所有下标遍历,不遍历数组的对象属性,但是会遍历到空元素,遍历时,下标都是数字
-
数组使用for in循环遍历会将所有的可枚举属性遍历,如果该属性没有值,就不遍历,例如数组中下标为空元素的,就不会被遍历,但是数组的对象属性会被遍历,遍历时,下标都会转换成字符串
for(var i=0;i<arr.length;i++){ console.log(arr[i]===undefined); console.log(i in arr);//可以判断对象中这个key对应的值是否存在,在数组中下标相当于对象中的key,可以判断它的对应值是否存在 } for(var index in arr){ console.log(arr[index]); } arr.a=10; arr.b=20; for(var i=0;i<arr.length;i++){ console.log(arr[i]);//取不到a,b属性的值 } for(var index in arr){ console.log(arr[index]);//能取到a,b的值 //使用for in会遍历到数组的属性 }
-
数组的方法
- Array.from()
- 从一个类似数组或可迭代对象创建一个新的,浅拷贝的数组实例
- push()
- 可以在数组尾部添加若干元素,返回新数组的长度
- pop()
- 删除数组尾部的最后一个元素,并将删除的元素返回
- 删除时可能会出现 3 次 undefined
- 删除空元素
- 删除 undefied
- 数组为空
- 循环删除数组中的元素
- 删除到某元素为止
while ( arr.pop() != 3 //删除到某元素为止 ) console.log(arr);- 如果数组中存储的是对象,必须将所有的对象引用全部设为 null
while (arr.length > 0) { arr[arr.length - 1] = null; arr.pop(); } //下面这种效率更高 for (var i = 0; i < arr.length; i++) { arr[i] = null; } arr.length = 0;
- 删除时可能会出现 3 次 undefined
- unshift()
- 在数组头部添加若干元素,并返回新数组的长度
- shift()
-
删除数组头部的一个元素,并返回删除的元素
-
以上四个方法执行后,原数组的引用关系不变,即都是改变原数组
- concat()
- 数组连接若干个数组或元素,返回一个新的连接完成的数组,与原数组没有引用关系
- join()
- 使用某个字符作为连接符,将所有数组元素连接成为一个字符串并返回这个字符串
- 如果没有连接符,默认逗号连接
- splice()
-
返回新数组,改变原数组
- 将数组中从第几位开始删除多少个元素,并返回被删除元素组成的新数组
- 将数组中从第几位开始删除多少个元素后,在该位置插入新的若干元素,返回删除元素组成的新数组
- 在数组的第几位开始插入若干个新元素,并返回空的新数组
var arr1 = arr.splice(1, 2); //将数组从第1位开始删除两个元素,并返回被删除的元素组成的新数组 console.log(arr1); var arr1 = arr.splice(1); //从第1位开始删除到尾部,并删除被删除元素组成的新数组 console.log(arr1); var arr1 = arr.splice(-1); //如果第一个参数是负数,从后向前开始,删除最后一位元素,并返回删除元素组成的数组 console.log(arr1); var arr1 = arr.splice(0); //将一个数组的所有元素转移到另一个数组中 console.log(arr1); var arr1 = arr.splice(); //没有删除,返回空数组 console.log(arr1); var arr1 = arr.splice(-3, 2); //从倒数第3个元素向后删除2个元素,并返回被删除元素组成的新数组 console.log(arr1); var arr1 = arr.splice(1, 2, -1, -2); //从第一位开始向后删除两个元素,并在该位置插入-1,和-2,并且返回被删除的两个元素组的数组 console.log(arr1); console.log(arr); var arr1 = arr.splice(1, arr.length, 0, -1, -2); //从第一位开始删除到尾部,并添加0,-1,-2 console.log(arr1); console.log(arr); var arr1 = arr.splice(1, 0, 0); //在第一位插入一个元素0,不删除,返回空数组 console.log(arr1, arr); for (var i = 0; i < arr.length; i++) { //在i不断增加的过程中,arr.length在不断减小,当arr.length减小到比i小时,循环结束 arr.pop(); } console.log(arr);- 数值型数组转数组的方法
var divs = document.getElementsByTagName("div"); var arr = Array.from(divs); var arr = Array.prototype.slice.call(divs); var arr = [].slice.call(divs); var arr = Array.prototype.concat.apply([], divs); var arr = [].concat.apply([], divs);
- slice
- 从第几项开始到第几项结束复制这些元素到新数组中,原数组不改变
var arr1 = arr.slice(0); //复制数组
console.log(arr1);
var arr1 = arr.slice(0); //复制数组
console.log(arr1);
var arr1 = arr.slice(); //复制数组
console.log(arr1);
var arr1 = arr.slice(-1); //从后向前数第一个元素到尾部的所有元素复制到新数组
console.log(arr1);
var arr1 = arr.slice(1, 2); //第一项复制到第二项,不包括结束
console.log(arr1);
- reverse()
- 当前数组按照倒装顺序将原数组颠倒,并返回原数组
- sort()
- 排序(字符规则),返回结果
- 改变原数组
arr.sort(function (a, b) {
return a - b; //从小到大排序
});
arr.sort(function (a, b) {
return b - a; //从大到小排序
});
- indexOf(要搜索的元素,从第几个下标开始)
-
不会改变原数组
-
在数组中查找元素,并返回该元素的下标,如果没有,返回-1
- lastIndexOf(要查找的元素,从什么位置开始查找)
-
不会改变原数组
-
从后向前查找元素
- fill(要填充的值,从什么位置开始,到什么位置之前结束)
- 只能用于有长度的数组,如果没有开始位置和结束位置,就会全部填充覆盖
- 填充对象会造成填充同一个引用地址的对象
数组的遍历
- forEach
- 遍历数组,可以获取使用元素和下标,自动过滤空元素,空元素不遍历
- 比 for in 来说,不会遍历到数组的属性
- 比 for 来说,不会遍历到空元素
- 缺点
- 函数中 this 指向会被改变
- map
- 遍历数组,使用 return 返回元素,这些被返回的元素会被放在一个新数组中,新数组的长度与原数组相同
- some()
- 判断数组是否有满足条件的元素,如果有直接跳出返回 true,否则返回 false
var bool = arr.some(function (item, index, arr) {
return item > 4;
});
- every()
- 遍历数组,判断每个元素是否满足条件,如果不满足直接跳出并返回 false,否则返回 true
var bool = arr.every(function (item, index, arr) {
return item > 0;
});
- filter()
- 筛选,将满足条件的元素放在一个新数组中,并且返回这个新数组
var arr1 = arr.filter(function (item) {
return item > 4;
});
- reduce() 归并
-
如果没有初始值,value 就是数组的第 0 个下标元素,后面的 item 则从下标为 1 开始遍历
-
遍历的第二次运行时,value 是上次运行 return 的返回值
-
当数组的每个元素遍历完成后,将最后一次的返回值,返回到外部变量
var arr = [1, 2, 9, 12, 5, 56, 7]; var sum = arr.reduce(function (value, item, index, arr) { return (value += item); }); console.log(sum); var arr = [1, 2, 9, 12, 5, 56, 7]; var max = arr.reduce(function (value, item, index, arr) { return value > item ? value : item; }); console.log(max); var min = arr.reduce(function (value, item) { return value < item ? value : item; }); console.log(min);
-
-
如果有初始值,value 的初始值就是设置的初始值,遍历将从第 0 项开始,item 从下标为 0 元素开始
var arr = [1, 2, 9, 12, 5, 56, 7]; var sum = arr.reduce(function (value, item) { return (value += item); }, 100); console.log(sum);
- flatMap()
-
扁平化数组
-
将二维数组扁平化为一维数组,存储在新数组中,只能在第一层进行
var arr = [[1, 2, 3],[4, 5, 6],[7, 8, 9]]; var arr1 = arr.flatMap(function (item) { console.log(item); return item; }); console.log(arr1);
-
-
注意
- 静态方法
- Array.from()
- 类的方法,处理一类的事务
- Array.from()
- 实例方法
- arr.filter()
- 实例的方法,针对一个实例处理事务
- arr.filter()
- 静态方法