JavaScript_函数
函数是一段可以复用的代码,可以独立独立存在,可以当作一个类使用,同时函数也是一个对象,本身是Function实例
函数定义方法
方法1:命名函数
function 函数名称(形参列表){
代码块;}
基本编程实现
<script>
//注意: js是弱类型编程语言,所以参数上没有类型,也没有返回值类型
function sayHello(name){
//修改<div id="div1">中的显示内容
var div1=document.getElementById("div1");
div1.innerHTML="你好,<strong>"+name+"</strong>!";
//函数可以有返回值,也可以没有
return 100;//如果没有返回值,则没有【return 值】的语句,或者【return;】
}
</script>
<div id="div1">原始显示内容</div>
<!-- 定义一个点击事件触发函数执行,函数不调用则不会执行。onclick属性值为调用函数的语句 -->
<button onclick="sayHello('yanjun')">点击</button>
方法2:匿名函数 function(形参){代码块;}
,另外特殊的还有ES6提供的箭头函数
<script>
var f=function(name){
var div1=document.getElementById("div1");
div1.innerHTML='Hello '+name+"!";
}
</script>
<div id="div1">原始显示内容</div>
<button onclick="f('yanjun')">点击</button>
方法3:使用Function类定义匿名函数 var f=new Function("形参列表","具体的代码块");
<script>
var f=new Function("name", "var
div1=document.getElementById('div1');
div1.innerHTML='hello'+name+'!';")
</script>
<div id="div1">原始显示内容</div>
<button onclick="f('yanjun')">点击</button>
三种方法的对比:
- 函数声明有预解析,而且函数的声明优先级高于变量
- 使用Function构造函数定义函数的方式是一个函数表达式,这种方式会导致两次解析,严重影响性能。第一次解析常规的JavaScript代码,第二次解析传入构造函数的字符串
递归函数
递归函数是一种允许在函数定义中调用自身函数的特殊函数。例如计算n的阶乘, n!=n*(n-1)!
- 在js函数中允许在不同的情况下返回不同类型的数据,但是一般不建议这种写法
var f=function(n){
if(typeof(n)=='number'){
if(n==1) return 1;
else return n*f(n-1);
}else
alert('参数类型错误');
}
使用递归调用计算n的阶乘
<script>
function jieCheng(num) {
alert(num instanceof Number);
//数据类型的判断可以使用typeof或instanceof两种方式判断
if ((!(num instanceof Number)) || num < 1) {
alert('参数错误!');
return -1;//返回-1用于表示计算出现错误,当然也可以使用throw new Error("")抛出异常
}
if (num == 1) return 1;
else
return num * jieCheng(new Number(num - 1));
}
// var res = jieCheng('5'); 报错 -1加上alert
var res=jieCheng(new Number('5'));
alert(res);
</script>
数据类型判断instanceof不正确,除非new Number(5),否则instanceof为false
<script>
function jieCheng(num) {
if (!(typeof(num)=='number') || num < 1) {
alert('参数错误!');
return -1;//返回-1用于表示计算出现错误,当然也可以使用throw new Error("")抛出异常
}
if (num == 1) return 1;
else
return num * jieCheng(num - 1);
}
// var res = jieCheng('5'); 报错 -1加上alert
var res=jieCheng(5);
alert(res);
</script>
编写JS的流程
- 布局:html+css 在写js之前必须保证有一个稳固的布局,这个布局本身不能有任何兼容问题
- 属性:确定要修改哪些属性 确定通过js修改哪些属性,例如display
- 事件:确定用户做哪些操作(产品设计)确定要在什么样的事件里修改,比如点击、移入移出
- 编写js:在事件中,用js来修改页面元素的样式
btn1.style.display='block' 将btn1的style的display的值设置为block
get Element By Id 通过id获取元素
<label onmouseover = “document.getElementById(‘btn1’).style.display=‘block’”
onmouseout = “document.getElementById(‘btn1’) .style.display=‘none’“>鼠标经过出现按钮</label>
局部的概念
在函数中定义的变量就是局部变量,仅仅在函数内部范围内有效,特殊问题就是提升问题。
<script>
function ff(){
var kk=99;
document.writeln("在ff函数内部调用kk="+kk+"<br/>");
}
//首先先调用函数。函数不会主动执行
ff();
document.writeln("在函数外部访问函数内的kk="+kk+"<br/>"); //Uncaught ReferenceError: kk is not defined
</script>
在函数外部定义的变量就是全局变量
- 局部变量只能在函数内部访问,全局变量可以在所有的函数里访问
- 局部变量在函数执行完毕后销毁,全局变量在页面关闭后销毁。
注意:在函数内没有使用var定义的变量也是全局变量
<script>
var kk=99;
function ff(){
// kk=100;
document.writeln("在ff函数内部调用kk="+kk+"<br/>");
}
ff();
document.writeln("在函数外部访问函数内的kk="+kk+"<br/>");
</script>
局部函数是在函数内部定义的函数,例如在outer函数中定义一个函数inner,则inner就是局部函数,outer则是全局函数。可以在outer函数内访问inner局部函数,但是在outer函数之外则无法访问内部局部函数inner
<script>
function outer(){
function inner(){
document.writeln("局部函数111<br/>");
}
document.writeln("开始测试局部函数...<br/>");
inner(); //调用内部局部函数
document.writeln("结束测试局部函数....")
}
outer();//调用外部函数
// 在外部函数之外不能调用内部函数,只能通过外部函数调用内部函数执行
inner(); //试图调用局部内部函数,内部函数.html:15 Uncaught eferenceError: inner is not defined
</script>
非要在外部函数之外访问内部函数的解决方案:
<script>
function ff(){
var kk=100;//在ff函数内部有效,局部变量
function show(){
//内部函数,一般外部函数之外不能直接访问
alert("内部函数中访问内部局部变量:"+kk);
}
// show();
return show; //返回内部函数,从而将show挂在到window对象上
}
var p=ff(); //接收ff函数的返回值,从而将p挂在到window对象
p();//实际上就是调用ff的内部函数show()
//ff();
//show();
</script>
闭包
闭包就是能够读取其它函数内部变量的函数,是将函数内部和函数连接起来的一个桥梁,即使外部函数已经执行完毕。
JavaScript 自带了垃圾回收机制,对于函数而言,在函数执行完毕后会被垃圾回收机制回收,进行内存释放,函数内部的局部变量也会被销毁,在内存中仅仅只能保存全局作用域
<script>
function ff(){
var kk=100;//局部变量,仅仅在ff的范围内有效
}
ff();
document.writeln(kk); //如果访问内部布局变量,则Uncaught ReferenceError: kk is not defined
</script>
为了在外部直接访问函数内部的变量或者直接调用内部的函数,可以引入闭包以延长内部变量的有效范围
<script>
function ff(){
var kk=100;//局部变量,仅仅在ff的范围内有效
return kk;
}
var p=ff();
document.writeln(p);
</script>
引入闭包的原因
- 使得外部得以访问函数内部的变量
- 避免全局变量的使用,防止全局变量污染
- 让关键变量得以常驻内存,免于被回收销毁
针对内部函数
<script>
function ff(){
function show(){
}
return show;
}
var p=ff();
p();
</script>
闭包的验证
function f1(){
var num=999;
add=function(){
num++;
}
function f2(){
alert(num);
}
return f2;
}
var res=f1();
add();//注意add由于没有使用var定义,所以add属于全局的
res();
add();
res();
注意点:
- 由于闭包会使得函数中的变量被保存到内存中,内存消耗会比较大,所以具体使用中注意不要滥用闭包。解决方案是:在推出函数之前,将不再使用的局部变量删除
- 闭包会在父函数外部改变父函数内部变量的值。会影响代码的封装性
全局和局部冲突
var sex=true;
function ff(){
console.log("ff before:"+sex);
var sex=123;
console.log("ff end:"+sex);
}
ff();
alert(sex);
自定义类
张老太养了两只猫猫:一只名字叫小白,今年3岁,白色;还有一只叫小花,今年10岁,花色。请编写一个程序,当用户输入小猫的名字时,就显示该猫的名字、年龄和颜色。如果用户输入的小猫名错误,则显示”张老太没有这只猫猫“。
以基础技术来解决
var cat1Name="小白";
var cat1age=3;
var cat1Color="白色";
var cat2Name="小花";
var cat2age=10;
var cat2Color="花色";
以面向对象技术来解决
//定义猫对象
function Cat(){
}
//定义主人
function Master(){
}
var master1=new Master();
master1.mName="张老太";
//实例化猫
var cat1=new Cat();
//!!特别说明: 在js中,你给它什么成员属性,是自由的.
cat1.cname="小白";
cat1.age=3;
cat1.color="白色";
cat1.master=master1;
var cat2=new Cat();
cat2.dname="小花";
cat2.age=100;
cat2.color="花色";
//访问一下
document.write(cat1.cname+" "+cat1["cname"]+" 它的主人是"+cat1.master["mName"]
);
访问对象公开属性的方式有两种:
- 对象名.属性名
- 对象名[‘属性名’]
类(原型对象)和对象(实例)的区别和联系
- 类(原型对象)是抽象的,概念的,代表一类事物,比如人、猫…
- 对象是具体的,实际的,代表一个具体事物
- 类(原型对象)是对象实例的模板,对象实例是类的一个个体
对象的属性
对象的属性,一般是基本数据类型(数、字符串),也可是另外的对象。比如前面创建猫对象的age就是猫对象的属性
this的用法
- 使用this关键字修饰的变量不再是局部变量,它是该函数的实例属性
function Person(name,age){
this.name=name; //实例对象每个实例都不同,可以通过new Person("",19).name的方式访问
Person.nation="汉族"; //类属性,是类的所有实例公用,只能通过Person.nation的方式访问,而不能使用new Person("yan",18).nation方式访问
var bb=0;//局部变量,外面不能访问,类似局部函数
cc=123;//全局变量
this.show=function(){
} //public的成员方法
Person.bbb=function(){
} //类成员
abc=function(){
} //全局方法
}
//注意:局部变量在函数执行完毕后销毁,全局变量在页面关闭后销毁,函数内没有使用var声明的变量(需要通过this.的方式)为全局变量
//直接定义一个对象
var p={
pp:function(){
for(var k=0;k<10;k++)
document.writeln("慢慢的走...");
}
}
//调用方式
p.pp();
js是一种动态语言,允许随时给对象增加属性和方法,直接为对象的某个属性赋值时就是给对象增加属性。
var obj={
}; //使用JSON语法创建一个对象
obj.name='zhangsan'; //向obj对象中添加一个属性name
obj.show=function(){
}; //向obj对象中添加一个成员方法show
总结:函数、方法、对象、类
JavaScript中函数定义后可以得到4项内容
- 函数:函数和 Java 中的方法一样,这个函数可以被调用。没有形参和实参一一对应的问题
<script>
var f = function (name, age) {
alert(name + "-->" + age);
//可以通过arguments获取所有请求参数
for(let kk in arguments){
document.writeln('参数['+kk+"]="+arguments[kk]+"<br/>");
}
}
f('yanjun',18,23); //实参只有一个,但是形参有2个,实际上传值是从左向右对应,如果缺少则默认传值为undefined
</script>
- 对象:定义一个函数,系统也会创建一个对象,这个对象就是Function类的实例
var f=function(){
}
alert(f instanceof Function);// true 表示f就是Function类型的变量,可以通过f()调用函数
alert(f instanceof Object);// true,表示f是Object类型的变量
alert(f instanceof String);//false,表示f不是String类型
针对对象,如果需要了解核心细节,可以使用console.log(f)输出/console.info
- 方法:定义一个函数时,这个函数通常会被附加给某个对象,作为对象的方法。如果没有明确指出将函数俯角到哪个对象上,该函数则将附加到window对象上,作为window对象的方法
var f=function(){
}
console.log(window);
window.f();
f();
- 类:定义函数时也得到一个与函数同名的类,该函数就是该类的唯一的构造器。所以实际上函数有直接调用和使用new关键字调用两种方法
var f=function(){
alert('hello javascript!');
}
//直接调用f();
//使用new运算符创建对象时,会自动执行function()中的定义,function()会当作类的构造器
var ff=new f();
alert(ff);
调用函数的4种方法
方法1:作为一个函数调用,基础语法【函数名(实参列表)】。函数实际上作为全局对象调用,会使this的值成为全局对象,使用window对象作为一个变量,容易造成程序崩溃
<script>
function bb(){
this.name="zhangsan";//实际上是将name定义为全局属性
console.log(this); //输出的应该是window对象
}
bb();
window.bb();
document.writeln("window.name="+window.name); //证明name称为全局属性
document.writeln("name="+name);//实际输出和window.name一致
</script>
方法2:函数作为方法调用。函数作为对象的方法进行调用,会是this的值称为对象本身
//使用JSON格式定义对象,JSON的语法格式为{key1:value1,key2:value2...}
var p1={
"username":"zhangsan", //定义属性,格式为【属性名:值】
"age":18,
show:function(k){
//定义方法,格式为【方法名称:function(形参){}】
console.log(this);
document.writeln(k+"---"+this.username+"---"+this.age);
}
};
//通过对象名.方法名的形式调用show函数
p1.show(12);
方法3:使用构造器的方式调用函数,构造函数中的this指向当前对象
function Person(){
this.name="zhangsan";//定义public属性
this.age;
this.show=function(){
console.log(this);
document.writeln("name:"+this.name+",age:"+this.age);
}
}
var p1=new Person(); //则function Person()会自动调用执行
alert(p1.name);//获取p1对象中的name属性值
alert(window.name);//不能获取到数据,因为Person中的this用于指代Person类型对象,不是window对象
p1.show();//调用成员方法
方法4:作为函数间接调用。实现有3种不同方式
类和对象
定义函数后实际上可以得到4项内容
- 函数。可以直接调用
- 对象。Function类型的实例
- 方法。函数会自动附加到某个对象
- 类。函数是类的唯一构造器
//前面已经进行了试验
function Person(name,age){
this.name=name; //实例属性,每个实例都不一样,可以通过【对象名.属性名】的方式进行访问
Person.nation="汉族"; //静态类属性,所有对象共享的属性,只能通过【类型名.属性名】的方式访问
Person.show = function () {
//静态类成员方法
console.log(this); //this可以使用,用于指代当前函数
console.log(this.username); //undefined
console.log(this.nation); //汉族
}
var bb=100; //局部变量或者理解为私有属性,不能通过对象名的方式进行访问,可以使用闭包函数访问
var ff=function(){
return bb;
}
return ff;
}
class关键字
ES6引入了class关键字可以用于定义类,但是需要考虑浏览器的支持问题
class Person{
constructor(){
//定义构造器函数,只能定义一个,名称固定,参数个数没有限制。如果需要定义多个构造器可以考虑使用arguments判断参数格式
if(arguments.length>1){
this.username=arguments[0];// this用于定义公共属性
this.age=arguments[1];
} else if(arguments.length==1){
this.username=arguments[0];
this.age=99;
} else {
this.username="zhangsan";
this.age=18;
}
}
//可以定义成员方法,规则是【方法名称(参数列表){}】
show(){
return "Hello "+this.username+",今年"+this.age+"岁了!";
}
}
var p=new Person();
document.writeln(p.show());
p=new Person("lisi",55,true); //按照处理逻辑并没有接收arguments[2],所以true没有使用
document.writeln(p.show());
函数间接调用的3种方式
直接调用 p.pp(1,2,3);
call方法动态调用函数
var ee=function(arr,fn){
//其中参数fn就是调用时才设置的函数
for(var index in arr){
fn.call(null,index,arr[index]); //调用函数fn,参数1调用的函数所属于的对象,如果值为null则表示window,后面参数则是调用函数时的参数
}
}
//调用ee函数
ee([2,3,4,5,6],function(index,ele){
//function()就是传递给fn的函数
document.writeln("第"+index+"个元素是"+ele+"<br/>");
});
apply动态调用函数
通过call调用函数时必须在括号种详细列出每个参数,但是通过apply动态调用函数时,可以在括号中以arguments代表所有参数
var ff=function(a,b){
alert(a+b);
}
ff.call(window,12,23); //12和23对应的就是函数中的参数,通过call方法动态调用时需要为每
个调用方法逐个的传入参数,写法为12,23
ff.apply(window,[12,23,34,56]);//通过apply方法动态调用方法时,能与arguments一次性传
入多个参数,例如这里的参数就是一个数组[12,23,34,56]
函数的参数处理
JavaScript 采用值传递方式,当通过实参调用函数时,传入函数里的不是实参本身,而是实参的不笨,因为在函数中修改参数值并不会对实参有任何影响。复合类型参数的是一个引用
<meta charset="UTF-8">
<script>
function ff(person) {
if (typeof (person) == 'object') {
person.age = 100;
} else alert('参数类型不是复合类型' + typeof person);
}
person={
"age":99,toString:function(){
return "age:"+this.age}};
ff(person);
document.writeln(person);
</script>
参数的新问题
在 JavaScript 中没有函数重载的概念,函数名称是函数的唯一标识,如果定义两个同名函数,则后盖前
function ff(person){
if(typeof(person)=='object'){
person.age=100;
} else
alert('参数类型不是复合类型'+typeof person);
}
ff(); //person形参对应的实参为undefined
原型
JavaScript 是基于原型的语言,原型可以理解为原件,通过精确地复制原件可以创建对象。JavaScript对象都有一个叫做原型的公共属性,这个原型属性就是构造函数的原型对象prototype。
function Person(){
this.name="Nicholas";
this.age=29;
this.job="Software Engineer";
this.sayName=function(){
}
}