复习
* break:在循环中遇到这个关键字,直接跳出当前所在的循环
continue:在循环中遇到这个关键字,直接进行下一次循环
*数组:存储组有序的数据,数据类型可以不一样
*数组的作用:一-次性存储多个数据
*数组元素:数组中存储的每个数据,叫数组元素,存储了5个数据,有5个数组元素
*数组的长度:数组的元素的个数,叫数组的长度:数组名. Length--->就是长度,数组的长度是可以改变的
*索引下标):存储数组元素的编号,从0开始,到数组的长度-1结束
*索引是用来存储和读取数组元素的
*遍历数组:通过循环的方式找到数组中的每个数据
*旨泡排序:
*求数组的和,最大值最小值,平均值
*
:函数:把一些重复的代码封装起来在需要的时候直接调用这个函数就可以了*函数作用:代码的重用
函数定义
function函数名(){
函数体
函数调用:
函数名()
*参数:形参和实参
*形参:函数定义的时候函数名字后面的小括号里的变量就是参数,是不需要写var
*实参:函数调用的时候小括号里传入的变量或者是值,都是丈参
*返回值:函数中如果有return,那么这个函数就有返回值
*如果函数中没有return,那么这个函数没有返回值
*如果函数中有return,但是后面什么内容都没有,这个函数没有明确返回值
*如果- 个函数没有明确的返回值,函数调用了,并且接收了,那么此时接收的结果undefined
*如果直接输出函数名字,那么是这个函数的代码
*
JS冒泡排序
原理
依次比较相邻的两个值,如果后面的比前面的小,则将小的元素排到前面。依照这个规则进行多次并且递减的迭代,直到顺序正确。
时间复杂度,空间复杂度,稳定性
- 平均时间复杂度O(n*n)
- 最好情况O(n)
- 最差情况O(n*n)
- 空间复杂度O(1)
- 稳定性:稳定
冒泡排序的写法
var examplearr=[8,94,15,88,55,76,21,39];function sortarr(arr){
for(i=0;i<arr.length-1;i++){
for(j=0;j<arr.length-1-i;j++){
if(arr[j]>arr[j+1]){
var temp=arr[j];
arr[j]=arr[j+1];
arr[j+1]=temp;
}
}
}
return arr;
}
sortarr(examplearr);
console.log(examplearr);
解析
两个循环
当i=0的时候,里面的循环完整执行,从j=0执行到j=6,这也就是第一遍排序,结果是将最大的数排到了最后,这一遍循环结束后的结果应该是[8,15,88,55,76,21,39,94]
当i=1的时候,里面的循环再次完整执行,由于最大的数已经在最后了,没有必要去比较数组的最后两项,这也是j<arr.length-1-i的巧妙之处,结果是[8,15,55,76,21,39,88,94]
说到这里,规律就清楚了,每次将剩下数组里面最大的一个数排到最后面,当第一个循环执行到最后的时候,也就是i=6,此时,j=0,只需要比较数组的第一和第二项,比较完毕,返回。
在js中使用函数注意三点:
1、函数被调用时,它是运行在他被声明时的语法环境中的;
2、函数自己无法运行,它总是被对象调用的,函数运行时,函数体内的this指针指向调用该函数的对象,如果调用函数时没有明确指定该对象, this 默认指向 window ( strict 模式除外,本文不涉及 strict 模式);
3、函数是一种带有可执行代码的对象类型数据。
一、声明函数
1、使用 function 关键字
复制代码代码如下:
function myfun(a,b){ //声明名为myfun的函数
return a+b;
}
2、 声明匿名函数
function(a,b){ return a+b;}匿名函数自身是无法保存的,由于在js中函数是一种对象型数据,因此可以把匿名函数赋给变量来保存。
var myfun = function(a,b){ return a+b;}
3、使用函数构造器Function //注意首字母大写
Function 是js内置的一个函数,他是所有函数对象的构造器。(其他数据对象也有自己的内置构造函数,比如Number,Object等,这些构造函数自己的构造器就是Function,因为他们都是函数)。
var myfun = new Function('a,b','return a+b;'); 其中最后一个参数是函数体,前面的参数都是函数的形式参数名,个数不定,因为需要用字符串传参来构造,函数较长时这种写法很不方便,一般很少用,也许你会用它来构造特定的返回值从而取代 eval函数。
需要注意的是,全局变量和全局函数都可以看作window对象的属性,如果存在同名的函数和变量,只能有一个生效(实际上只有一个属性),试试下面的代码。
复制代码代码如下:
function a(){ alert('a');}
alert(window.a); //访问window对象的属性也可以省去window不写
var a=1;
alert(window.a);
函 数和变量的声明都发生在代码解析期,不同的是,变量在解析期只声明不赋值,因此,同一个作用域内存在同名的函数和变量时,在代码运行期执行到变量赋值之 前,同名函数生效,同名变量赋值之后(用新的数据覆盖了该window对象属性原有的值),变量生效(但是要注意,在firefox 下, 在 with 伪闭包内声明的函数,只能在声明之后才能被调用,即,firefox 的 with 内没有对函数预先声明)。
复制代码代码如下:
with({}){
a(); //在 firefox 下 a 是未声明
function a(){ console.log("function a is called");}
}
如果同名称的函数被多次声明,后面声明的将覆盖前面声明的,如:
复制代码代码如下:
alert(func1);//弹出func1(){alert(2);}
func1(){
alert(1);
}
alert(func1); //弹出func1(){alert(2);}
func1(){ //这是最后一次声明的func1,以该函数为准
alert(2);
}
alert(func1); //弹出func1(){alert(2);}
var func1 = function(){ //注意 ,这里是变量赋值,不是函数声明
alert(3);
}
alert(func1); //弹出function(){alert(3);}
二、函数的参数
js函数不会检查函数调用时传入的参数个数与定义他时的形式参数个数是否一致,一般地,js函数调用时可以接收的参数个数为25个,当然不同的浏览器可能有差异,ECMAScript标准对这一点并没有规范。
如果你不确定函数调用时传入了多少个参数,可以使用函数的arguments对象。
arguments 有点像数组,arguments.length 为传入的参数个数,arguments[0] 是第一个参数,arguments[1]是第二个参数,类推...
函数对象的length属性:这个属性很少用到,甚至很少人知道,函数的length属性就是该函数定义时的形式参数个数。
复制代码代码如下:
function myfun(a,b){
alert(arguments.length); //弹出调用时实际传入的参数个数
alert(arguments[0]); //对应参数a
return a+b;
}
alert(myfun.length); //形参个数,2
arguments对象还有其他属性,比如常用的arguments.callee ,指向该函数自身。
要注意:如果函数内部声明了与形参同名的子函数(同域内,变量未赋值时同名函数生效),arguments 的相应值也会被修改,但是,在作用域内使用 var 声明了同名的 变量则不会导致 arguments 的参数值被函数替换(但firefox 依然替换)。
复制代码代码如下:
function aa(a , b,c){ //js 群的一道题
function a(){}
console.log(a); //function a
console.log(aa);
//如果作用域内没有 var a ,则 arguments[0] 为 function a (friefox(version 17) 则一定是function a)
console.log(arguments[0]);
var a = "ee"; //注销此句,考擦 arguments[0] 将变为 a 函数
var aa = "444";
arguments = 6;
console.log(a);
console.log(aa);
console.log(arguments);
}
aa(1,2,3);
一、在函数调用的时候,浏览器每次都会传递进两个隐式参数
- 函数的上下文对象this
- 封装实参的对象arguments
二、arguments 对象
- arguments 对象实际上是所在函数的一个内置类数组对象
- 每个函数都有一个arguments属性,表示函数的实参集合,这里的实参是重点,就是执行函数时实际传入的参数的集合。arguments不是数组而是一个对象,但它和数组很相似,所以通常称为类数组对象,以后看到类数组其实就表示arguments。arguments对象不能显式的创建,它只有在函数开始时才可用。
- arguments还有属性callee,length和迭代器Symbol。
- arguments同样具有length属性,arguments.length 为函数实参个数,可以用arguments[length]显示调用参数
- arguments对象可以检测参数个数,模拟函数重载
三、理解点
第一点:arguments对象:可以在函数内访问所有的参数,实参
实例代码:
function f1(){
console.log(arguments[0]);
console.log(arguments[1]);
console.log(arguments[2]);
}
f1(12,23,45); //12 34 45
第二点:在正常的模式下,arguments对象可以在运行的时候进行修改
实例代码:
function f2(a,b){
arguments[0] = 10;
arguments[1] = 20;
return a + b;
}
console.log(f2(4,6)); //30
第三点:在严格的模式下,arguments对象在运行的时候不可以修改,修改arguments对象不会影响到实际的函数参数
注意: 'use strict' 是开启严格模式
实例代码:
function f3(a,b){
'use strict';
arguments[0] = 10;
arguments[1] = 20;
return a + b;
}
console.log(f3(3,6)); //9
第四点:通过arguments对象的length属性,可以判断实参的个数
实例代码:
function f4(){
console.log(arguments.length);
}
f4(2,3,4,5); //4
f4(1); //1
f4(); //0
第五点:arguments是一个对象,不是数组,转换为数组可以采用 slice 和 逐一填入新数组
实例代码:
var arr = Array.prototype.slice.call(arguments);
console.log(typeof arr);
var arr2 = [];
for(var i = 0; i<arguments.length;i++){
arr2.push(arguments[i]);
}
console.log(typeof arr2);
第六点:arguments的callee属性可以返回对应的原函数,达到调用自身函数的效果,但是在严格模式中是不适用的
实例代码:
var f5 = function(){
console.log(arguments.callee===f5); //true
console.log(arguments.callee===f6); //false
}
var f6;
f5(); //返回原函数,调用自身
四、arguments的应用
第一点:arguments.callee为引用函数自身。我们可以借用arguments.length可以来查看实参和形参的个数是否一致
实例代码:
function add(a, b) {
var realLen = arguments.length;
console.log("realLen:", arguments.length);
var len = add.length;
console.log("len:", add.length);
if (realLen == len) {
console.log('实参和形参个数一致');
} else {
console.log('实参和形参个数不一致');
}
};
add(11,22,33,44,66);
第二点:我们可以借用arguments.callee来让匿名函数实现递归
实例代码:
var sum = function(n) {
if(n == 1) {
return 1;
} else {
return n + arguments.callee(n-1);
}
}
console.log("sum =", sum(5));
第三点:编写一个函数求传入的若干个数字(不能用数组显示传入)的和
实例代码:
function add() {
var len = arguments.length; var sum = 0;
for (var i = 0; i < len; ++i) {
sum += arguments[i];
} return sum;
};
add(11,22,33,44,66);
四、函数调用
函数自己是不会运行的,当它运行时,总是存在一个调用它的对象。
默认情况下,在任何语法环境中,如果没有显式指定函数的调用对象,就是指通过window对象来调用该函数,此时,函数体内的this指针指向window对象。
复制代码代码如下:
function myfun(a,b){
alert(this);
return a+b;
}
myfun(1,2); // 调用函数并传入2个参数,这2个参数分别对应形式参数a,b调用函数时,如果传入的参数个数超过形式参数,就只有用arguments加下标来接收了。
由于没有显式指定调用函数的对象,alert(this)将弹出 window对象。这种调用方法是最常见的。
用于显式指定函数的调用对象方法有三个:
1、如果一个函数被赋为一个对象的属性值,这个函数只能通过该对象来访问(但并非是说该函数只能被该对象调用),通过该对象调用这个函数的方式类似以面向对象编程语言中的方法调用(实际上在js中也习惯使用方法这种称呼)。
复制代码代码如下:
var obj={}; //定义一个对象
obj.fun=function(a,b){
alert(this); //弹出this指针
return a+b;
} //对象属性值为函数
alert(obj.fun);// 访问fun函数。 只能通过该对象来访问这个函数
obj.fun(1,2); //通过obj对象来调用fun函数,将弹出obj对象。这种方式也称为调用obj对象的fun方法。
2、 任意指定函数的调用对象:在某个语法环境中,如果可以同时访问到函数fun和对象obj,只要你愿意,可以指定通过obj对象来调用fun函数。指定方法 有2种:call方法和apply方法。(因为window对象是浏览器环境下的顶级对象,在任何语法环境中都能访问到window对象,因此,任何函数 都可以通过window对象来调用)
复制代码代码如下:
function fun(a,b){
alert(this);
return a+b;
}
var obj={};
fun.call(obj,1,2); //通过obj对象来调用fun函数,并传入2个参数,弹出的指针为obj对象。
var obj2={};
obj2.fun2 = function(a,b){ //obj2对象的属性fun2是一个函数
alert(this);
return a+b;
};
obj2.fun2.call(obj,1,2); //通过obj对象来调用obj2对象的fun2属性值所保存的函数,弹出的this指针是obj对象
//比较隐蔽的方法调用:数组调用一个函数[9,function(){ alert(this[0]); }][1]();
//使用window对象调用函数下面几种方法是等价的
fun(1,2);
window.fun(1,2); //如果fun函数是全局函数
fun.call(window,1,2);
fun.call(this,1,2); //如果该句代码在全局环境下(或者被window对象调用的函数体内),因为该语法环境下的this就是指向window对象。
func.call(); //如果函数不需要传参
func.call(null,1,2);
func.call(undefined,1,2);var name = "window";
function kkk(){
console.log(this.name); // not ie
}
kkk(); //window
kkk.call(kkk); //kkk 函数被自己调用了
另一种比较容易疏忽的错误是,在A 对象的方法中,执行了使用了 B 对象的方法调用,试图在 B 对象的方法里使用 this 来访问 A 对象,这在各种回调函数中比较常见,最常见的情形就是 ajax 回调函数中使用 this 。
复制代码代码如下:
var obj = {
data:null,
getData:function(){
$.post(url,{param:token},function(dataBack){ //jQuery ajax post method
this.data = dataBack; //试图将服务器返回的数据赋给 obj.data ,但这里的 this 已经指向 jQuery 的 ajax 对象了
},'json');
}
}
//正确做法
var obj = {
data:null,
getData:function(){
var host = this; //保存 obj 对象的引用
$.post(url,{param:"token"},function(dataBack){
host.data = dataBack;
},'json');
}
}
3、apply方法调用:
apply方法与call方法唯一不同的地方是函数传参方式不同。
obj2.fun2.call(obj,1,2); 改为 apply方式就是obj2.fun2.apply(obj,[1,2]);
apply使用类数组方式传参,除数组外,还可以使用arguments、HTMLCollection来传参,但arguments并非数组,如:
var obj={};
function fun_1(x,y){
function fun_2(a,b){
return a+b;
}
fun_2.apply(obj,arguments); //用fun_1的arguments对象来传参,实际上是接收了x,y
}apply 传参在IE8 及IE8一下的浏览器中哟2个问题
在 call 和 apply 调用中,如果传入标量数据(true/false ,string,number),函数运行时将把他们传入的基本数据包装成对象,然后把this指向包装后的对象,试试下面的代码。
function a(){
alert(typeof this);
alert(this.constructor);
alert(this);
}
a.call(false);
a.call(100);
a.call('hello');
甚至可以用这个特点来传参数,但是不建议这种用法:
function a(){ alert(1+this); } //对象在运算中自动进行类型转换
a.call(100); //101
4、函数作为对象构造器
当函数使用 new 运算作为对象构造器运行时,this 指向新构造出对象,如果该构造函数的返回值不是 null 以外的对象,构造函数运行完毕将返回 this 指向的对象,否则返回原定义的对象。
复制代码代码如下:
function Fun(){
this.a = 1;
this.b = 3;
console.log(this); //{a:1,b:2}
// return {a:999}; //加上此举 ,将返回 {a:999}
}
var obj = new Fun(); //obj = {a:1,b:2} ,如果没有参数,也可以写成 var obj = new Fun;
五、函数作用域
js的变量作用域是函数级的,在js里没有类似c语言的块级作用域。
js编程环境的顶级作用域是window对象下的范围,称为全局作用域,全局作用域中的变量称为全局变量。
js函数内的变量无法在函数外面访问,在函数内却可以访问函数外的变量,函数内的变量称为局部变量。
js函数可以嵌套,多个函数的层层嵌套构成了多个作用域的层层嵌套,这称为js的作用域链。
js作用域链的变量访问规则是:如果当前作用域内存在要访问的变量,则使用当前作用域的变量,否则到上一层作用域内寻找,直到全局作用域,如果找不到,则该变量为未声明。
注意,变量的声明在代码解析期完成,如果当前作用域的变量的声明和赋值语句写在变量访问语句后面,js函数会认为当前作用域已经存在要访问的变量不再向上级作用域查找,但是,由于变量的赋值发生的代码运行期,访问的到变量将是undefined.
如:
复制代码代码如下:
var c=1000;
function out(){
var a=1;
var b=2;
function fun(){
alert(a); //undefined
var a=10;
alert(a); //10
alert(b); //2
alert(c); //1000
}
fun();
}
out();
六、匿名函数的调用
匿名函数的使用在js很重要,由于js中一切数据都是对象,包括函数,因此经常使用函数作为另一个函数的参数或返回值。
如果匿名函数没有被保存,则运行后即被从内存中释放。
匿名函数的调用方式一般是直接把匿名函数放在括号内替代函数名。如:
(function(a,b){ return a+b;})(1,2); //声明并执行匿名函数,运行时传入两个参数:1和2
//或者
(function(a,b){ return a+b;}(1,2));
//下面这种写法是错误的:
function(a,b){ return a+b;}(1,2);
由于js中语句结束的分号可以省略,js引擎会认为function(a,b){ return a+b;}是一句语句结束,因此匿名函数只声明了没有被调用,如果语句没有传参(1,2)写成(),还会导致错误,js中空括号是语法错误。
下面这种写法是正确的。
var ab = function(a,b){ return a+b;}(1,2); // ab=3
js 解析语法时,如果表达式出现在赋值运算或操作符运算中,是"贪婪匹配"的(尽量求值)
function(t){ return 1+t;}(); //error
var f = function(t){ return t+1;}(); // ok
~ function(t){return t+1;}(); //ok
+ function(t){return t+1;}(); //ok
七、字符串实时解析中的函数调用:eval()、new Function()、setTimeout()、setInterval()
eval() 与 window.eval()
复制代码代码如下:
function a(){
console.log('out of b');
}
function b(){
function a(){ console.log("in b"); }
var f = function(){ a(); };
eval('a()'); // in b
window.eval('a()'); //out of b ,ie 6\7\8 in b, ie 9 out of b
(new Function('a();'))(); //out of b
setTimeout('a()',1000); // out of b
setTimeout(f,2000);// in b
}
b();
eval() 中的代码执行于eval() 语句所处的作用域内:
复制代码代码如下:
var Objinit = function(){
var param = 123;
return {
execute:function(codes){
eval(codes);
},
setCallback:function(f){
this.callback = f;
},
fireCallback:function(){
this.callback && this.callback.call(this);
},
getParam:function(){
return param;
}
}
};
var obj = Objinit ();
var param = 'outerParam';
console.log(param,obj.getParam()); //outerParam 123
obj.execute('param = 456');
console.log(param,obj.getParam()); //outerParam 456
obj.setCallback(function(){ eval("param = 8888")});
obj.fireCallback();
console.log(param,obj.getParam()); //8888 456
obj.setCallback(function(){ eval("eval(param = 9999)")});
obj.fireCallback();
console.log(param,obj.getParam()); //9999 456eval()
字符串中解析出的代码运在 eval 所在的作用域,window.eval() 则是运行在顶级作用域(低版本 chrome 和 低于IE9 则同 eval()).
IE 中 ,window.execScript(); 相当于 window.eval()
new Function()、setTimeout()、setInterval() 的第一个字符串参数所解析得到的代码,都是在顶级作用域执行