JS基础-4

234 阅读19分钟

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
何为内置
1arguments只有函数才有
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];//每一轮循环获取当前传递的那个实参值
        //为了防止字符串+数字是字符串拼接而不是数学的累加,我们最好把其他数据类型先转换为number类型
        cur = Number(cur);
        //为了防止传递的是非 有效数字(数字+ NaN = NaN),我们最好做一下非有效数字的检测:有效数字才进行累加
        if(isNaN(cur) === false)  {
            total += cur;
        }
    }
    return total;
}

1.5 函数return

"闭包"的保护机制导致私有作用域会保护里面的私有变量
function sum() {
var total = null;
......
return total;
//return后面跟着的都是值(返回的都是值)此处不是把TOTAL变量返回,而是把TOTAL存储的值返回而已 <==> return 60
return 1+1 ; return 2;
}
sum:代表的是函数本身
sum():让函数先执行,代表的是当前函数返回的结果(return 后面是啥,相当于函数返回的是啥)
如果函数中没有写return 或者return后面啥也没有,默认返回的结果就是undefined
在函数体中遇到return后,return后面的代码就不再执行了
var total = sum()//外面是全局下的total和函数中的total没有必然的联系

1.6 匿名函数

没有名字的函数
- 函数表达式
- 自执行函数
oBox.onclick = function() {
//把一个没有名字的函数(有名字也无所谓)作为值赋值给一个变量或者一个元素的某个事件等: 函数表达式
}
//前边最好加个分号 防止代码压缩出现问题 当作函数执行部分 
;(function(n)  {
//创建函数和执行函数放在一起,创建完成立马执行  .自执行函数.
//n 形参 n =10
})(10)
//以下都是自执行函数 符号只是控制语法规范
~function(n){}(10)
-function(n){}(10)
+function(n){}(10)
!function(n){}(10)

2.数组

2.1 数组基础结构

数组的基础结构:
数组也是对象数据类型的 typeof [] object
数组也有属性名,只不过属性名是数字,我们把数字属性名称之为它的索引:数组是以数字作为索引,索引从零开始,有一个length属性代表数组的长度

类数组:类似于数组 但是不是数组
1、通过getElementsByTagName 获取的元素集合是类数组
2、函数中的实参集合arguments也是类数组
遍历数组
1for循环操作
2for in 操作
for(var key in ary)  {
//key:属性名(数组中的属性名是索引)
console.log(ary[key])
}
//for 循环只能遍历到数组私有的一些属性,而for in 循环可以把一些自定义的公共属性也能遍历到(Array.prototype.aa=100)

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) {
//数组中有12
}

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) {
    //数组中有多少项,当前回调函数执行多少次,每一次传递进来的value就是当前遍历数组这一项的值,index就是遍历这一项的索引
})
ary.map(function(value,index) {
    //遍历数组中的每一项,在forEach的基础上,可以修改每一项的值
    return xxx;//return 后面返回的结果就是把当前遍历的这一项修改为xxx
})
filter
find
reduce
every

2.3 数组去重

方案一:双循环方式
遍历数组中的每一项,拿每一项和它后面的项依次比较,如果相同了,则把相同的这一项在原来的数组中删除即可
var ary = [1,2,3,3,4,5,3,2,1]
//ary.length-1:最后一项的后面没有内容了,我们不需要再比较
for(var i = 0; i < ary.length-1; i++) {
    var cur = ary[i];//当前遍历的这一项(索引i)
    //把拿出的这一项和后面的每一项进行比较
    //i+1:把当前项和它后面项比较,当前项索引是i,后一项索引是i+1
    for(var j = i+1; j < ary.length; j++) {
        //ary[j]:作比较的那一项
        if(cur ===ary[j]) {
            //本次作比较的这一项和当前项相同,我们需要在原有数组中把作比较的这一项删除掉(作比较的这一项的索引是)
            ary.splice(j,1);
            j--;//先让j-- ,然后再j++,相当于没加没减,此时j还是原有索引,再获取的时候就是删除这一项后面紧挨着的这一项
        }
    }
}
除了j-- 另外的一种方法:修改内层循环
for(var j = i+1; j < ary.length;) {
    if(cur === ary[j]) {
        ary.splice(j,1);
    }else {
        j++;
    }
//或者 cur === ary[j] ? ary.splice(j,1) : j++;
}

数组塌陷问题:我们使用splice删除数组中的某一项后,删除这一项后面的每一项索引都要向前进一位(在原有索引上减1),此时如果我们j++,循环操作的值累加了,我们通过最新j获取的元素不是紧挨删除这一项的元素,而是跳过这一项获取的元素
外层循环9次 
内层循环19  28   37   相当于循环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);//使用splice会导致后面的索引向前进一位,如果后面有很多项,消耗的性能很大
        i--;
        //替换思路:我们把最后一项拿过来替换当前要删除的这一项,然后再把最后一项删除
        //ary[i] = ary[ary.length-1];
        //ary.length--;
        //i--;
        continue;
}
obj[cur] = cur;// obj[1] = 1 {1:1}存储
}
//最佳方案 放到原型链上
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++) {
            //ary[j]:当前本次拿出来这一项
            // ary[j+1]:当前项的后一项
            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];
    //splice返回的是个数组,数组中包含了删除的那个内容
    //用剩余数组中的每一项和中间项进行比较,比中间项大的放在右边,比它小的放在左边(左右两边都是新数组)
    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++)  {
            //handAry[j]:当前比较的手里的这张牌
            // 新抓的牌比当前比较的这张牌大了,我们把新抓的牌放在它的后面
            if(item > handAry[j]) {
                handAry.splice(j+1,0 item);
                break;
            }
            if(j === 0) {
                //新抓的牌是最小的,我们把新抓的牌放在最开始的位置
                handAry.unshift(item);
            }
        }
    }
    return handAry;
}
  • 动力: 这是我的学习笔记(来源于视频或者查阅文档),您能从中得到收获和进步,是我分享的动力,帮助别人,自己也会更快乐
  • 期望: 不喜勿喷,谢谢合作!如果涉及版权请及时联系我,马上删除!