一. 函数
Javascript 是一门基于对象的脚本语言, 代码复用的单位是函数, 但它的函数比结构化程序设计语言的函数功能更丰富. JavaScript 语言中的函数是 "一等公民", 它可以独立存在; 而且 JavaScript 的函数完全可以作为一个类来使用(而且它还是该类唯一的构造器); 与此同时, 函数本身也是一个对象, 函数本身是 function
实例.
函数的最大作用是提供代码复用, 将需要重复使用的代码块定义成函数, 提供更好的代码复用. 函数可以有返回值, 可以没有返回值
1. 定义函数的三种方式
a)定义命名函数,
语法格式如下:
- function functionName(param1,param1,...){
- staments;
- }
b)定义匿名函数
语法格式如下:
- function(parameter list){
- staments
- };
与命名函数的区别是没有函数名, 函数后面有个分号.
当通过这种语法格式定义了函数之后, 实际上就定义了一个函数对象(即 function 实例), 接下来可以将这个对象赋给另外一个变量. 例如下面代码:
- <script type='text/javascript'>
- var f = function(name)
- {
- document.writeln('匿名函数 < br/>');
- document.writeln('你好'+name);
- }
- f('yukey');
- </script>
使用匿名函数提供更好的可读性.
c)使用 function 类匿名函数
JavaScript 提供了一个 function 类, 该类也可以用于定义函数, Function 类的构造器的参数个数可以不受限制, function 可以接受一系列的字符串参数, 其中
最后一个字符串参数是函数的执行体, 执行体的各语句以分号 (;) 隔开, 而前面的各字符串参数则是函数的参数, 看下面定义函数的方式:
- <script type='text/javascript'>
- var f = new Function('name',"document.writeln('Function 定义的函数 < br/>');"
- +"document.writeln('你好'+name);");
f('yukey');
</script>
2. 局部函数
局部函数在函数里定义, 看下面代码
- <script type="text/javascript">
- function outer(){
- function inner1(){
- document.write("局部函数 11111<br/>");
- }
- function inner2(){
- document.write("局部函数 22222<br/>");
- }
- inner1();
- inner2();
- document.write("结束测试局部函数 < br/>");
- }
- outer();
- document.write("调用 outer 之后...");
- </script>
在外部函数里调用局部函数并不能让局部函数获得执行的机会. 只有当外部函数被调用时, 外部函数里调用的局部函数才获得执行的机会.
3. 数, 方法, 对象, 变量和类 函数是 JavaScript 的 "一等公民", 函数是 JavaScript 变成里非常重要的一个概念, 当使用 JavaScript 定义了一个函数之后, 实际上可以得到如下四项.
函数: 就像 Java 的方法一样, 这个函数可以被调.
对象: 定义一个函数时, 系统也会创建一个对象, 该对象时 Function 类的实例
方法: 定义一个函数时, 该函数通常会附加给某个对象, 作为该对象的方法
变量: 在定义一个函数的同时, 也会得到一个变量
类: 在定义函数的同时, 也得到一个与函数同名的类
定义函数之后, 有如下两种方式调用函数
直接调用函数: 直接调用函数总是返回该函数体内最后一条 return 语句的返回值; 如果该函数体内不包含 return 语句, 则直接调用函数没有返回值.
使用 new 关键字直接调用函数: 通过这种方式调用总有返回值, 返回值就是一个 Javascript 对象.
- <script type="text/javascript">
- var test = function(name)
- {
- return "你好,"+name;
- }
- var rval = test('Sherman');
- var obj = new test("Sherman");
- alert( rval+"\n"+obj);
- </script>
可以看出, 第一种方式直接调用函数, 返回的是 return 语句返回值, 第二种使用 new 关键字调用给函数, 也就是将函数当成类来使用, 得到的是一个对象.
下面程序定义了一个 person 函数, 也就定义了一个 person 类, 该 person 函数也会作为 Person 类唯一的一个构造器, 定义了 person 函数时希望为该函数定义
了一个方法.
- <script type="text/javascript">
- function Person(name ,age){
- this.name = name;
- this.age = age;
- this.info=function(){
- document.writeln("我的名字是"+this.name+'<br/>');
- document.writeln("我的年纪是"+this.age+'<br/>');
- }
- }
- var p = new Person('Sherman',24);
- p.info();
- </script>
被 this 关键字修饰的的变量不再是局部变量, 它是该函数的实例属性.
JavaScript 定义的函数可以 "附加" 到某个对象上, 作为该对象的方法. 实际上如果没有明确指定将函数 "附加" 到哪个对象上, 该函数默认 "附加" 到 window
对象上, 作为 window 对象的方法.
例如如下代码:
- <script type="text/javascript">
- function hello(name)
- {
- document.write(name+", 您好 < br/>")
- }
- window.hello("孙大圣");
- p = {
- // 定义一个函数, 该函数属于 p 对象
- walk:function(){
- for(let i = 0 ; i <2 ; i++){
- document.write("慢慢地走...<br/>");
- }
- }
- };
- p.walk();
- </script>
4. 函数的实例属性和类属性
由于 JavaScript 函数不仅仅是一个函数, 而且是一个类, 该函数还是此类唯一的构造器, 只要在调用函数时使用 new 关键字, 就可返回一个 object, 这个 object
不是函数的返回值, 而是函数本身产生的对象. 因此 JavaScript 中定义的变脸不仅有局部变量, 还有实例属性和类属性两种. 根据函数中声明变量的方式,
中的变量有三种
a)局部变量: 在函数中以 var 声明的变量
b)实例属性: 在函数中以 this 前缀修饰的变量
c)类属性: 在函数中以函数名前缀修饰的变量
局量只能在函数里访问的变量. 实例属性和类属性是面向对象的概念: 实例属性是属于单个对象的, 因此必须通过对象来访问, 类属性是属于整个类本身
(也就是函数)的, 因此必须通过类来访问.
同一个类只占用一块内存, 因此每个类属性只占用一块内存; 同一个类每创建一个对象, 系统将为该对象的实例属性分配一块内存.
- <script type="text/javascript">
- function Person(national,age){
- this.age = age;
- Person.national = national;
- var bb = 0;
- }
- var p1 = new Person('中国',29);
- with(document){
- writeln("创建第一个 Person 对象");
- writeln("p1 的 age 属性为:"+p1.age+"<br/>");
- writeln("p1 的 national 属性为:"+p1.national+"<br/>");
- writeln("通过 Person 访问静态 national 属性为:"+Person.national+"<br/>");
- writeln("p1 的 bb 属性为"+p1.bb+"<br/><hr/>");
- }
- var p2 = new Person('美国',32);
- with(document){
- writeln("创建两个 Person 对象中后 < br/>");
- writeln("p1 的 age 属性为:"+p1.age+"<br/>");
- writeln("p1 的 national 属性为:"+p1.national+"<br/>");
- writeln("p2 的 age 属性为:"+p2.age+"<br/>");
- writeln("p2 的 national 属性为:"+p2.national+"<br/>");
- writeln("通过 Person 访问静态 national 属性为:"+Person.national+"<br/>");
- }
- </script>
浏览器输出:
创建第一个 Person 对象 p1 的 age 属性为: 29
p1 的 national 属性为: undefined
通过 Person 访问静态 national 属性为: 中国
p1 的 bb 属性为 undefined
创建两个 Person 对象中后
p1 的 age 属性为: 29
p1 的 national 属性为: undefined
p2 的 age 属性为: 32
p2 的 national 属性为: undefined
通过 Person 访问静态 national 属性为: 美国
值得指出的是, JavaSript 和 java 不一样, 它是一种动态语言, 它允许随时为对象增加属性和方法, 当直接为对象的某个属性赋值时, 即可视为给对象增加属性
5. 调用函数的 3 种方式
5.1 直接调用函数
如下代码:
- // 调用 window 对象的 alert 方法
- window.alert();
- // 调用 p 对象的 walk 方法
- p.walk();
当程序使用 window 对象调用方法时, window 调用者可以省略
5.2 以 call 方式调用函数
直接调用函数的方式简单易用, 但这种调用方式不够灵活, 有时候调用函数时需要动态的传入一个函数引用, 此时为了动态地调用函数, 就需要 call 方法. call 调用函数
语法格式为
函数引用. call(调用者, 参数 1, 参数 2,...)
下面通过 call 方法调用 each 函数:
- <script type="text/javascript">
- var each = function(array,fn){
- for(var index in array){
- fn.call(null,index,array[index]);
- }
- }
- each([4,20,3],function(index,ele){
- document.writeln("第"+index+"个元素是:"+ele+"<br/>");
- });
- </script>
浏览器输出:
第 0 个元素是: 4
第 1 个元素是: 20
第 2 个元素是: 3
5.3 以 apply()方法调用函数
apply()方法和 call()方法比较类似, 都可以动态的调用函数, 他们的区别是:
a)通过 call()方法调用函数时, 必须在括号中列出每个参数
b)通过 apply()动态地调用函数时, 需要以数组形式一次性传入所有调用函数
以下代码示范了 call()和 apply()的关系
- <script type="text/javascript">
- var myfun = function(a,b){
- alert('a 的值是'+a+'\nb 的值是'+b);
- }
- // 以 call()方式动态的调用函数
- myfun.call(window,5,20);
- // 以 apply()方式动态的调用函数
- myfun.apply(window,[3,12]);
- var example = function(num1,num2){
- // 直接用 arguments 代表调用 example 函数时传入的所有函数
- myfun.apply(this,arguments);
- }
- example(20,40);
- </script>
由此可见, apply()和 call()对应关系如下:
函数引用. call(调用者, 参数 1, 参数 2,...); = 函数引用. apply(调用者,[参数 1, 参数 2,...]);
6. 函数独立性
虽然定义函数时可以将函数定义成某个类的方法, 或定义成某个对象的方法. 但 JavaScript 的函数是 "一等公民", 他永远是独立的, 函数永远不会从属于其他类, 对象.
下面代码示范了函数的独立性:
- <script type="text/javascript">
- function Person(name){
- this.name = name;
- this.info = function (){
- alert("我的 name 是:"+this.name);
- }
- }
- var p = new Person("Sherman");
- // 调用 p 对象的 info 方法
- p.info();
- var name = "测试名称";
- // 以 window 对象作为调用者来调用 p 对象的 info 方法
- p.info.call(window);
- </script>
当使用匿名内嵌函数定义某个类的方法是时, 该内嵌函数一样是独立存在的, 该函数也不是作为该类实例的附庸存在, 这些内嵌函数也可以被分离出来独立使用, 成为另一个对象的函数. 如下代码再次证明函数的独立性:
- <script type="text/javascript">
- function Dog(name,age,bark)
- {
- this.name = name;
- this.age = age;
- this.bark = bark;
- // 使用内嵌函数为 Dog 实例定义方法
- this.info = function(){
- return this.name+"的年龄为:"+this.age+", 它的叫声为:"+this.bark;
- }
- }
- var dog = new Dog("旺财",3,"汪汪, 汪汪...");
- function Cat(name,age){
- this.name = name;
- this.age = age;
- }
- // 将 dog 实例的 info 方法分离出来, 在通过 call 方法调用 info 方法
- // 此时 cat 为调用者
- var cat = new Cat("Kitty",2)
- alert(dog.info.call(cat));
- </script>
7. 函数提升
JavaScript 允许先调用函数, 然后再在后面定义函数, 这就是典型的函数提升: JavaScript 会将全局函数提升到根元素 < script.../>元素的顶部定义
例如如下代码:
- <script type="text/javascript">
- console.log(add(2,5));
- function add(a,b){
- console.log("执行 add 函数")
- return a+b;
- }
- </script>
和下面代码效果是一样的:
- <script type="text/javascript">
- function add(a,b){
- console.log("执行 add 函数")
- return a+b;
- }
- console.log(add(2,5));
- </script>
效果如图:
如果使用程序先定义匿名函数, 然后将匿名函数赋值给变量, 在这种方式下依然会发生函数提升, 但此时只提升被赋值的变量, 函数定义本省不提升. 例如
- <script type="text/javascript">
- console.log(add(2,5));
- var add = function(){
- console.log("执行 add 函数");
- return a+b;
- }
- </script>
效果如图:
局部函数会被提升到所在函数的顶部, 如
- <script type="text/javascript">
- function test() {
- function add(a, b) {
- console.log("执行 add 函数")
- return a + b;
- }
- console.log(add(2, 5));
- }
- test();
- </script>
JavaScript 编程时应尽量避免变量名和函数名同名. 否则会发生覆盖的情形: 分两种情况
a)定义变量时只用 var 定义变量, 不分配初始值, 此时函数的优先值更高, 函数会覆盖变量.
b)定义变量时为变量值指定了初始值, 此时变量的优先值更高, 变量会覆盖函数
测试代码如下:
- <script type="text/javascript">
- function a(){}
- var a;
- console.log(a);
- var b;
- function b(){}
- console.log(b);
- var c = 1;
- function c(){};
- console.log(c);
- function d(){}
- var d = 1;
- console.log(d);
- </script>
效果如下:
8. 箭头函数
箭头函数相当于其他语言的 Lambda 表达式或闭包语法, 箭头函数是普通函数的简化写法. 语法格式如下:
(param1,param2,param3,...) => {staments}
相当于定义了如下函数:
function(param1,param2,param3,...){}
如果箭头函数的执行体只有一条 return 语句, 则允许省略函数执行体的花括号和 return 关键字.
如果箭头函数的形参只有一个参数, 则允许省略形参列表的圆括号.
如果箭头函数没有形参, 则圆括号不可以省略.
- (param1,param2,param3,...) => expression
- // 等同于(param1,param2,param3,...) =>{return expression}
- singleParam => {staments}
- // 等同于(singleParam) => {staments}
下面代码示范了箭头函数代替传统函数:
- <script type="text/javascript">
- var arr = ["yuekey","fkit","leegang","sczit"];
- var newArr1 = arr.map(function(ele){
- return ele.length;
- });
- var newArr2 = arr.map((ele) =>{return ele.length});
- var newArr3 = arr.map(ele => ele.length);
- console.log(newArr3);
- arr.forEach(function(ele){
- console.log(ele);
- });
- arr.forEach((ele) => {console.log(ele);})
- arr.forEach(ele => console.log(ele));
- </script>
与普通函数不同的是, 箭头函数并不拥有自己的 this 关键字, 对于普通函数而言, 如果程序通过 new 调用函数创建对象, 那么该函数中的 this 代表所创建的对象;
如果直接调用普通函数, 那么该函数的 this 代表全局对象(window). 例如, 如下代码示范了 this 关键字的功能.
- <script type="text/javascript">
- function Person(){
- this.age = 0;//Person()作为构造器使用时, this 代表构造器创建的对象
- setInterval(function growUp(){
- console.log (this=== window);// 对于普通函数来说, this 代表全局对象 window, 总是返回 true
- this.age++;
- },1000);
- }
- var p = new Person();
- setInterval(function(){
- console.log(p.age);// 此处访问 p 对象的 age, 总是 0
- },1000);
- </script>
箭头函数中的 this 总是代表包含箭头函数的上下文, 例如:
- <script type="text/javascript">
- function Person(){
- this.age = 0;
- setInterval(() =>
- {
- console.log(this === window);
- this.age++;//this 总是代表包含箭头函数的上下文
- },1000);
- }
- var p = new Person();
- setInterval(() => console.log(p.age),1000);// 此处访问的是 p 对象的 age, 总是不断加 1
- </script>
如果在全局范围内定义箭头函数, 那么箭头函数的上下文就是 window 本身, this 代表全局对象 window 对象.
- <script type="text/javascript">
- var f = () => {return this;};
- console.log(f() === window);// 输出 true
- </script>
箭头函数并不绑定 arguments, 因此不能在箭头函数中通过 arguments 来访问调用箭头函数的参数, 箭头函数的 arguments 总是引用当前上下文的 arguments. 例如
- <script type="text/javascript">
- var arguments = "Sherman";
- var arr = () => arguments;
- console.log(arr());
- function foo(){
- var f = (i) => 'Hello,'+arguments[1];
- return f(2);
- }
- console.log(foo("Sherman","Leegang"));// 箭头函数中的 arguments 引用当前上下文的 arguments, 此时代表调用 foo 函数的参数
- </script>
9. 函数的参数处理
大部分时候, 函数都需要接受参数传递. 与 Java 完全类似, JavaScript 的参数传递也全部采用值传递方式.
9.1 基本类型和复合类型的参数传递
对于基本类型参数, JavaScript 采用值传递方式, 当通过实参调用函数时, 传入函数里的并不是实参本身, 而是实参的副本, 因此在函数中修改参数值并不会对实参
有任何影响:
- <script type="text/javascript">
- function change(arg1) {
- arg1 = 10;
- document.writeln("函数执行中 arg1 的值为:" + arg1 + "<br/>");
- }
- var x = 5;
- document.writeln("函数调用前 x 的值为"+x+"<br/>");
- change(x);
- document.writeln("函数调用之后的 x 值为"+x+"<br/>");
- </script>
对于复合类型的参数, 实际上采用的依然是值传递方式, 只是很容易混淆. 看如下程序
- <script type="text/javascript">
- function changeAge(person)
- {
- person.age = 10;
- document.writeln("函数执行中 age 的值为:" + person.age + "<br/>");
- person = null;
- }
- var person = {age:5};
- document.writeln("函数调用之前 age 的值为"+person.age+"<br/>");
- changeAge(person);
- document.writeln("函数调用之后的 age 值为"+person.age+"<br/>");
- document.writeln("person 对象为"+person);
- </script>
9.2 空参数
在 JavaScript 中, 在函数声明时包含了参数, 但调用时没有传入实参, 这种情况是允许的, JavaScript 会自动将参数值设置为 undefined 值, 对于 JavaScript 来说, 函数名就是函数的唯一标识.
如果先后定义两个同名, 形参列表不同的函数, 这不是函数重载, 这种情况后面定义的函数会覆盖前面的函数.
9.3 参数类型
JavaScript 是弱类型语言, 参数列表无需声明参数类型.
"鸭子类型" 的理论认为, 弱类型语言的函数需要接收参数时, 则应先判断参数类型, 判断参数是否包含了需要访问的属性, 方法. 当条件都满足时 , 程序才会真正
执行. 看如下代码
- <script type="text/javascript">
- function changeAge(person) {
- if (typeof person == 'object' && typeof person.age == 'number') {
- document.writeln("函数调用之前 age 的值为" + person.age + "<br/>");
- person.age = 10;
- document.writeln("函数执行中 age 的值为:" + person.age + "<br/>");
- }
- else {
- document.writeln("参数类型不符合" + typeof person + "<br/>")
- }
- }
- changeAge();
- changeAge("Sherman");
- changeAge(true);
- p = {abc:34};//json 格式创建第一个对象
- changeAge(p);
- person = {age:25};//json 格式创建第二个对象
- changeAge(person);
- </script>
来源: https://www.cnblogs.com/yumiaoxia/p/8893505.html