1.JS中的函数
1.1 函数
函数是指一段在一起的、可以做某一件事儿的程序。也叫做子程序、(oop中)方法。
函数是实现某一个功能的方法。
创建函数
function [函数名](){
// [函数体]
//实现功能的具体JS代码
}
执行函数
函数名()//把创建函数的执行,而且这个函数可以执行很多次
函数名();
每一次执行都相当于把函数体中实现功能的JS代码重复执行了一遍
在真实项目中,我们一般都会把实现一个具体功能的代码封装在一个函数里
1、如果当前这个功能需要在页面中执行多次,不封装成为函数,每一次想实现这个功能,都需要重新把代码写一遍,浪费时间,而封装在一个函数中,以后想多次实现这个功能,我们就没必要重写代码了,只需要把函数重新执行即可,提高了开发效率
2、封装在一个函数中,页面中基本上很难出现重复的代码,减少了页面上代码冗余度,提高了代码了重复利用率:"低耦合 高内聚"
我们把以上特点称为"函数封装"(oop面向对象编程的思想,需要我们掌握的就是类的继承、封装、多态)
JS的函数核心原理
函数作为JS中引用数据类型中的一种,也是按照引用地址来操作的
1.2 创建函数
首先会在当前作用域中声明一个函数名(声明的函数名和使用var声明的变量名是一样的操作: var sum;function sum;这两个名字算重复的)
- 浏览器首先会开辟一个新的内存空间(分配一个16进制地址),把函数体中写好的代码当作普通字符串存储在这个内存空间中(创建一个函数如果不执行,函数没有意义)
- 把内存空间的地址赋值给之前声明的那个函数名
函数执行
目的:把之前存储的实现具体功能的JS代码执行
函数执行,浏览器首先会为其开辟一个新的'私有作用域'(只能执行函数中之前编写的JS代码)
- 形参赋值
- 私有作用域中的变量提升
- 把之前创建时候存储的那些JS代码字符串,拿到私有作用域中,然后把它们变为JS表达式从上到下执行
1.3 闭包
函数执行会形成一个私有的作用域,让里面的私有变量和外界互不影响(相互不干扰、外面的无法直接获取里面的变量值),此时我们可以理解为私有作用域把私有变量保护起来的,我们把这种保护机制称之为"闭包"
栈内存
作用域:(全局作用域/私有作用域)提供一个供JS代码执行的环境
堆内存
所有的引用数据类型,它们需要存储的内容都在堆内存中(相当于一个仓库,目的是存储信息)
- 对象会把键值对存储进来
- 函数会把代码当作字符串存储进来
1.4 函数中的形参和实参
形参:相当于生产洗衣机的时候提供的入口,需要用户执行函数的时候把需要的值传递进来,形参是个变量,用来存储和接收这些值
实参:用户执行的时候传递给形参的具体值
形参:形参变量 类似于var了一下
定义了形参但是执行的时候没有传递实参,默认实参的值是undefined
判断undefined 的两种形式 if(num === undefined) if(typeof num === "undefined")
typeof num === "undefined" ?num = 0 : null
arguments 实参集合
当我们不知道用户具体要传递几个值的时候(传递几个值都行),此时我们无法设置形参的个数:遇到此类需求,需要使用函数内置的的实参集合:arguments
何为内置
1、arguments只有函数才有
2、不管执行函数的时候是否传递实参,arguments天生就存在,没有传递实参ARG是个空的集合,传递了ARG中包含了所有传递的实参值
3、不管是否设置了形参,ARG中始终存储了所有的实参信息
arguments是一个类数组集合
arguments[0] 第一个实参信息
arguments[1] 第二个实参信息
arguments[n] 第n+1个实参信息
有一个length属性,存储的是当前几个的长度(当前传递的实参的个数)
arguments[length]
arguments['length']
arguments.callee:存储的是当前函数本身
arguments.callee.caller:存储的是当前函数在哪执行的(宿主函数),在全局作用域下执行的,结果是null
一般真正项目中很少使用:因为在严格的JS模式下不允许我们使用这两个属性,然而现有项目大部分都是基于严格模式来的
function sum() {
var total = null;
for(var i = 0; i < arguments.length; i++) {
var obj = arguments[i];
cur = Number(cur);
if(isNaN(cur) === false) {
total += cur;
}
}
return total;
}
1.5 函数return
"闭包"的保护机制导致私有作用域会保护里面的私有变量
function sum() {
var total = null;
......
return total;
return 1+1 ; return 2;
}
sum:代表的是函数本身
sum():让函数先执行,代表的是当前函数返回的结果(return 后面是啥,相当于函数返回的是啥)
如果函数中没有写return 或者return后面啥也没有,默认返回的结果就是undefined
在函数体中遇到return后,return后面的代码就不再执行了
var total = sum()
1.6 匿名函数
没有名字的函数
- 函数表达式
- 自执行函数
oBox.onclick = function() {
}
;(function(n) {
})(10)
~function(n){}(10)
-function(n){}(10)
+function(n){}(10)
!function(n){}(10)
2.数组
2.1 数组基础结构
数组的基础结构:
数组也是对象数据类型的 typeof [] object
数组也有属性名,只不过属性名是数字,我们把数字属性名称之为它的索引:数组是以数字作为索引,索引从零开始,有一个length属性代表数组的长度
类数组:类似于数组 但是不是数组
1、通过getElementsByTagName 获取的元素集合是类数组
2、函数中的实参集合arguments也是类数组
遍历数组
1、for循环操作
2、for in 操作
for(var key in ary) {
console.log(ary[key])
}
2.2 数组中有很多常用方法
console.dir(Array.prototype)
1、方法的意义和作用
2、方法的形参
3、方法的返回值
4、通过此方法,原来的数组是否发生了变化
实现数组的增加、修改、删除
增加
1、push:向数组的末尾追加新内容
参数:一到多个,任何数据类型都可以,想要给数组末尾增加什么,直接传递到push方法中即可,传递多个用逗号隔开
返回值:新增后数组的长度
原有数组改变了
2、unshift:向数组开头追加内容
参数:需要追加的内容(可以是多个任何数据类型的值)
返回值:新增后数组的长度
原有数组改变了
3、把数组当作一个普通的对象,使用对象键值对的操作,给其设置新的属性(索引)
ary[ary.length] = xxx;向数组末尾追加了新的内容
删除
1、pop:删除数组最后一项
参数:无
返回值:被删除的那一项内容
原有数组改变了
2、shift:删除数组第一项
参数:无
返回值:被删除那一项的内容
原有数组改变了
使用shift删除第一项之后,后面每一项的索引都要向前进一位(导致后面项的索引发生改变)
3、把数组当作普通的对象操作
delete删除:delete ary[索引] 删除指定索引这一项(当前项被删除后,原有数组其他项的索引不会改变:当前数组的length也不会改变)
ary.length-- :删除数组最后一项 数组长度随之发生变化
splice:数组中内置的方法,可以实现数组的增加、修改、删除
splice 实现删除
splice(n,m):从索引n开始删除m个(m不写是删除到数组的末尾)
返回值:被删除的内容(以一个新数组保存)
原有数组改变了
splice(0) 清空数组
splice() 一项都不删除,返回一个新的空数组
splice 实现修改
splice(n,m,x):在原有删除的基础上,用x代替删除的内容
splice实现增加
splice(n,0,x):在修改的基础上,我们一项都不删除,把x插入到索引n的前面
ary.splice(0,0,x) 向数组开头追加新的内容
ary.splice(ary.length,0,x) 向数组末尾追加新元素
slice:数组地查询
参数:slice(n,m) 从索引n开始找到索引为m处(不包含m)
返回值:把找到的部分以一个新数组返回
原来的数组不变
slice(n)从索引n开始找到末尾
slice(0)/slice() 数组克隆,克隆一份和原来数组一模一样的新数组 ==>空间地址不一样
slice 支持负数索引,如果传递的索引为负数,浏览器解析的时候是按照 总长度+负数索引 来处理的
concat:将多个数组拼接在一起
参数:要拼接的内容(把内容放在原数组的后面),可以是一个数组,也可以是一些数据值
返回:拼接后的新数组
原有数组不发生变化
ary.concat([1,5],7,"帅哥")
concat() 什么都没有拼接,相当于把原有数组克隆一份一模一样的新数组出来
将数组转化为字符串
toString:实现把数组转换为字符串(转换后的字符串以逗号分隔每一项)
参数:无
返回值:转换的字符串
原有数组不变
ary = [1,2,3]
ary.toString()
"1,2,3"
jpin:把数组按照指定的分隔符转换为字符串,和字符串中的split相对应
参数:指定的链接符
返回值:转换后的字符串
原有数组不变
ary = [12,23,34,45,56,67,78,89]
ary.join()
"12,23,34,45,56,67,78,89"
ary.join('')
"122334344556677889"
ary.join(' ')
"12 23 34 45 56 67 78 89"
ary.join('+')
"12+23+34+45+56+67+78+89"
已知数组中的每一项都是数字,想实现数组求和,我们如何实现?
1、循环实现
var total = null;
for(var i = 0; i < ary.length;i++) {
total +=ary[i];
}
2、利用join
ary.join('+');
var total = eval(ary.join('+'))
把字符串变为JS表达式执行
reverse:把数组中的每一项倒过来排列
参数:无
返回值:排列后的数组
原有数组改变
sort:实现数组的排序
参数:无或者回调函数
返回值:排序后的数组
原有数组改变
不传递参数的情况下:可以给10以内的数字进行升序排列,但是超过10的就无法处理了(多位数只识别第一位)
ary.sort(function(a,b) {
return a-b;
return b-a;
})
验证数组中是否包含某一项
indexOf /lastIndexOf:获取当前项在数组中的第一次或者最后一次出现位置的索引
数组中的这两个方法在IE6-8下不兼容
字符串中的这两个防范兼容所有的浏览器
如果当前数组中并没有这一项,返回的索引是-1,我们根据这一点可以验证数组中是否包含这一项
if(ary.indexOf(12) >-1) {
}
Array.prototype.myIndexOf = function myIndexOf(value) {
var result = -1;
for(var i = 0; i < this.length; i++) {
if(value === this[i] ) {
result = i;
break;
}
}
return result;
}
遍历数组中的每一项
以下方法在IE6-8 下都不兼容
forEach:遍历数组中的每一项
ary.forEach(function(value,index) {
})
ary.map(function(value,index) {
return xxx;
})
filter
find
reduce
every
2.3 数组去重
方案一:双循环方式
遍历数组中的每一项,拿每一项和它后面的项依次比较,如果相同了,则把相同的这一项在原来的数组中删除即可
var ary = [1,2,3,3,4,5,3,2,1]
for(var i = 0; i < ary.length-1; i++) {
var cur = ary[i];
for(var j = i+1; j < ary.length; j++) {
if(cur ===ary[j]) {
ary.splice(j,1);
j--;
}
}
}
除了j-- 另外的一种方法:修改内层循环
for(var j = i+1; j < ary.length;) {
if(cur === ary[j]) {
ary.splice(j,1);
}else {
j++;
}
}
数组塌陷问题:我们使用splice删除数组中的某一项后,删除这一项后面的每一项索引都要向前进一位(在原有索引上减1),此时如果我们j++,循环操作的值累加了,我们通过最新j获取的元素不是紧挨删除这一项的元素,而是跳过这一项获取的元素
外层循环9次
内层循环1、 9 2、 8 3、 7 相当于循环1+2+3+...+9 再加外层循环9次
方案二:
利用indexOf来验证当前数组中是否包含某一项,包含把当前项删除掉(不兼容IE6-8)
for(var i = 0;i < ary.length; i++) {
var cur = ary[i];
var curNextAry = ary.splice(i+1);
if(curNextAry.indexOf(cur)>-1) {
ary.splice(i,1);
i--
}
}
方案三:
遍历数组中的每一项,把每一项作为新对象的属性名和属性值存储起来,例如:当前项1,对象中存储的是{1:1}
在每一次向对象中存储之前,首先看一下原有对象中是否包含了这个属性(typeOf obj[xxx] === 'undefined' 说明当前对象中没有xxx这个属性),如果已经存在这个属性说明数组中的当前项是重复的(1-在原有数组中删除这一项 2-不再向对象中存储这个结果),如果不存在(把当前项作为对象的属性名和属性值存储进去即可)
var obj = {};
for(var i = 0; i < ary.length;i++) {
var cur = ary[i];
if(typeof obj[cur] !=='undefined') {
ary.splice(i,1);
i--;
continue;
}
obj[cur] = cur;
}
Array.prototype.myUnique = function myUnique() {
var obj = {};
for(var i = 0; i < this.length; i++) {
var item = this[i];
if(typeof obj[item] !='undefined') {
this[i] = this[this.length-1];
this.length--;
i--;
continue;
}
obj[item] = item;
}
obj = null;
return this;
}
相邻比较法
首先给数组进行排序,然后相邻两项比较,相同的话,把后一项在数组中去掉
2.4 数组排序
冒泡排序
原理:让数组的当前项和后一项进行比较,如果当前项大于后一项,我们让两者交换位置(小--> 大)
具体比较的轮数:ary.length - 1 数组有多长,我们需要把总长度-1个数分别放在末尾,即可实现最后的排序
每一轮比较的次数 ary.length -1 (不用和自己比) - 当前已经执行的轮数(执行一轮向末尾放一个最大值,这些值不需要再比较)
function bubble(ary) {
for(var i = 0; i < ary.length - 1; i++) {
for(var j = 0; j < ary.length -1 - i; j++) {
if(ary[j] > ary[j+1]) {
var temp = ary[j];
ary[j] = ary[j+1];
ary[j+1] = temp;
}
}
}
}
递归: 函数自己调用自己 且有尽头
需求:1-10以内的所有偶数乘积
function fn(num) {
if(num < 1) {
retrun 1;
}
if(num%2 === 0) {
return num * fn(num-1);
}
return fn(num-1);
}
var result = fn(10);
return 10 * fn(9) ===> return 10 * fn(8) ===> return 10 * 8 * fn(7) ===> return 10 * 8 * fn(6) ===> retrun 10 * 8 * 6 * fn(5) ===> return 10 * 8 * 6 * fn(4)
return 10 * 8 * 6 * 4 * fn(3) ===> return 10 * 8 * 6 * 4 * fn(2) ===> return 10 * 8 * 6 *4 * 2 * fn(1) ===> return 10 * 8 * 6 *4 *2 *fn(0) ===>return 10 * 8 * 6 * 4 * 2 * 1
需求:递归实现1- 100 内 能被15整除的数之和
function fn1() {
if(num >100) {
return 0;
}
if(num % 15 === 0) {
return num + fn(num + 1);
}
return fn(num + 1);
}
fn1(1)
1--> fn1(2)
2 --> fn1(3);
...
15 --> fn1(15); ==> 15 + fn1(16);
fn1(16) ---> fn1(17) ==> 15 + fn1(17);
fn1(30) ==> 15 + 30 + fn1(31);
15 + 30 + 45 + 60 + 75 + 90 + fn(101)
15 + 30 + 45 + 60 + 75 + 90 + 0
快速排序
先找到中间这一项,然后将剩余项中的每一个值和中间项进行比较,比它小的放在左侧(新数组),比它大的放在右侧(新数组)
function quick(ary) {
if(ary.length < = 1) {
return ary;
}
var centerIndex = Math.floor(ary.length/2);
var centerValue = ary.splice(centerIndex,1)[0];
var aryLeft = [];
var aryRight = [];
for(var i = 0; i < ary.length; i++) {
var cur = ary[i];
cur < centerValue ? aryLeft.push(cur) : aryRight.push(cur);
}
return quick(aryLeft).concat(centerValue,quick(aryRight))
}
quick(12,15,14,13,16,11)
return quick([12,13,11]).concat(14,quick([15,16]))
quick([12,13,11]) ===> quick([12,11]).concat(13,quick([]))
quick([12,11]) ===> quick([11]).concat(12,quick([]))
quick([11]) ===> 11
quick([]) ===>[]
到底了
反推
quick([11]).concat(12,quick([])) ===> [11].concat(12,[]) ===> [11,12]
quick([12,11]) ===> [11,12]
quick([12,11]).concat(13,quick([])) ===>[11,12].concat(13,[]) ===> [11,12,13]
quick([12,13,11]) ===> [11,12,13]
quick(12,15,14,13,16,11) ===>得到 return quick([12,13,11]).concat(14,quick([15,16]))
执行完quick([12,13,11]) 返回 然后执行 quick([15,16])
即执行quick([12,13,11]) 还没有给quick([15,16]) 生成调用栈
等quick([12,13,11])执行完 才有quick([15,16])的调用栈
插入排序
在桌面上新抓一张牌A,我们用A和我们手里已经抓的牌进行比较(个人习惯从右向左比),如果A比手里当前要比较的这张牌小,则继续向左比较,一直遇到比手里当前
要比较的这张牌大,我们把A放在当前手里这张牌的后面
如果新抓的牌A比手里的所有牌都要小,我们把它放在最前面即可
function insert(ary) {
var handAry = [];
handAry.push(ary[0]);
for(var i = 1; i < ary.length; i++) {
var item = ary[i];
for(var j = handAry.length -1 ; j >= 0;j++) {
if(item > handAry[j]) {
handAry.splice(j+1,0 item);
break;
}
if(j === 0) {
handAry.unshift(item);
}
}
}
return handAry;
}
- 动力: 这是我的学习笔记(来源于视频或者查阅文档),您能从中得到收获和进步,是我分享的动力,帮助别人,自己也会更快乐
- 期望: 不喜勿喷,谢谢合作!如果涉及版权请及时联系我,马上删除!