[JavaScript] function

1. Definition and function calls

1.1 Defined Functions

ABS function (x) { 
    IF (x> = 0) { 
        return x; 
    } the else { 
        return the -X-; 
    } 
} 

function: function key definitions;
ABS: function name
(x): x is a function of the parameters, a plurality of parameters in the partition
{...}: between the block body of the function

 

Statement inside the body of the function, when executed, perform a return once, the function is executed is finished, and returns the results. If no return statement, the function will be executed after completion of returned results, only the result is undefined

 

The second function is defined by:

var abs = function (x) {
    if (x >= 0) {
        return x;
    } else {
        return -x;
    }
};

 

Both define exactly equivalent, pay attention to the second way in accordance with the complete syntax need to add at the end of a function body; indicates the end of an assignment statement.

 

 1.2 calling function

When you call the function, passing parameters in order to:

 

 Since JavaScript allows you to pass arbitrary parameters without affecting the call, so the incoming parameters defined by many parameters than there is no problem, although the internal functions do not require these parameters:

abs1(10, 'acb', 'xiaoming');
10

 Incoming less than the defined parameters is not a problem:

abs1 (); 
NaN

 When the function expects to be passed parameters, did not pass the call reference, this time ABS1 (x) function will receive parameters x undefined, the calculation result is NaN

To avoid receiving undefined, you can check the parameters:

 

arguments

 This keyword can only function in the current internal use, it is to get all function parameters passed by the caller.

 

Use arguments, you can get all the parameters by incoming calls. In other words, even if the function does not define any parameters, you can still get the value of the parameter:

 

 rest parameters

 Since the JavaScript function allows to receive any number of parameters, so we had to use argumentsto get all the parameters:

function foo(a, b) {
    var i, rest = [];
    if (arguments.length > 2) {
        for (i=2; i < arguments.length; i++) {
            rest.push(arguments[i]);
        }
    }
    console.log('a = ' +a);
    console.log('b = ' +b);
    console.log(rest);
}

 

Such an approach, the logic yes, but slightly cumbersome, ES6 standard introduces the rest parameter, the above parameters can be rewritten as:

function foo(a, b, ...rest) {
    console.log('a:', a);
    console.log('b:', b);
    console.log('rest:', rest)
}

 

注意 rest 在函数参数部分的写法:(a, b ...rest)  这样就能直接获取除了 a, b 以外的所有参数。

当除了 a, b 参数以外,没有 rest 参数,查看下返回结果:

 

测试-1:

用rest参数编写一个sum()函数,接收任意个参数并返回它们的和。

function foo(...rest) {
    var j=0;
    for (var i of rest) {
        // console.log(i);
        j += i;
    }
    return j;
}

 

测试-2:

定义一个计算圆面积的函数area_of_circle(),它有两个参数:

  • r: 表示圆的半径;
  • pi: 表示π的值,如果不传,则默认3.14
function area_of_circle(r, pi) {
    return pi?pi:3.14 * r **2;
    // if (pi === undefined) {
    //     pi = 3.14;
    // }
    // return pi * r**2;
}

 

 

变量作用域和解构赋值

如果一个变量在函数体内部申明,则该变量的作用域为整个函数体,在函数体外不可引用该变量:

 

在来看一个函数内部的嵌套函数,判断变量的使用:

这说明JavaScript的函数在查找变量时从自身函数定义开始,从“内”向“外”查找。如果内部函数定义了与外部函数重名的变量,则内部函数的变量将“屏蔽”外部函数的变量。

 

变量提升

JavaScript的函数定义有个特点,它会先扫描整个函数体的语句,把所有申明的变量“提升”到函数顶部:

 

在 JavaScript 中, 函数调用的时候需要做两步:

  1. 分析(AO对象)

    (1) 先分析有没有参数

    (2) 查看有没有局部变量

    (3) 查看有没有声明函数

 

例 - 1:

 

例 - 2

(1)首先进行词法分析:

(2)执行函数

执行函数时,不会对内部子函数进行赋值,直接跳过。

由于JavaScript的这一怪异的“特性”,我们在函数内部定义变量时,请严格遵守“在函数内部首先申明所有变量”这一规则。最常见的做法是用一个var申明函数内部用到的所有变量:

function foo() {
    var
        x = 1, // x初始化为1
        y = x + 1, // y初始化为2
        z, i; // z和i为undefined
    // 其他语句:
    for (i=0; i<100; i++) {
        ...
    }
}

 

全局作用域

不在任何函数内定义的变量就具有全局作用域。实际上,JavaScript默认有一个全局对象window,全局作用域的变量实际上被绑定到window的一个属性:

 

 因此,直接访问全局变量course和访问window.course是完全一样的。

 

名字空间

全局变量会绑定到window上,不同的JavaScript文件如果使用了相同的全局变量,或者定义了相同名字的顶层函数,都会造成命名冲突,并且很难被发现。

减少冲突的一个方法是把自己的所有变量和函数全部绑定到一个全局变量中。例如:

// 唯一的全局变量MYAPP:
var MYAPP = {};

// 其他变量:
MYAPP.name = 'myapp';
MYAPP.version = 1.0;

// 其他函数:
MYAPP.foo = function () {
    return 'foo';
};

 把自己的代码全部放入唯一的名字空间MYAPP中,会大大减少全局变量冲突的可能。

 

局部作用域

 为了解决块级作用域,ES6中引入了新的关键字 let,用 let 代替 var 可以申明一个块级作用域的变量:

 

解构赋值

从ES6开始,JavaScript引入了解构赋值,可以同时对一组变量进行赋值。

什么是解构赋值?我们先看看传统的做法,如何把一个数组的元素分别赋值给几个变量:

var array = ['hello', 'JavaScript', 'ES6'];
var x = array[0];
var y = array[1];
var z = array[2];

 现在,在ES6中,可以使用解构赋值,直接对多个变量同时赋值:

如果数组本身还有嵌套,也可以通过下面的形式进行解构赋值,注意嵌套层次和位置要保持一致:

解构赋值还可以忽略某些元素:

 

如果需要从一个对象中取出若干属性,也可以使用解构赋值,便于快速获取对象的指定属性:

var person = {
    name: '小明',
    age: 20,
    gender: 'male',
    passport: 'G-12345678',
    school: 'No.4 middle school'
};
var {name, age, passport} = person;

console.log(name);  // 小明
console.log(age);   // 20
console.log(passport);  // G-12345678

 

对一个对象进行解构赋值时,同样可以直接对嵌套的对象属性进行赋值,只要保证对应的层次是一致的:

var person = {
    name: '小明',
    age: 20,
    gender: 'male',
    passport: 'G-12345678',
    school: 'No.4 middle school',
    address: {
        city: 'Beijing',
        street: 'No.1 Road',
        zipcode: '100001'
    }
};

var {name, address:{city, zip}} = person;

console.log(name); // '小明'
console.log(city); // 'Beijing'
console.log(zip); // undefined, 因为属性名是zipcode而不是zip
// 注意: address不是变量,而是为了让city和zip获得嵌套的address对象的属性:
console.log(address); // Uncaught ReferenceError: address is not defined

 

使用解构赋值对对象属性进行赋值时,如果对应的属性不存在,变量将被赋值为undefined,这和引用一个不存在的属性获得undefined是一致的。如果要使用的变量名和属性名不一致,可以用下面的语法获取:

var person = {
    name: '小明',
    age: 20,
    gender: 'male',
    passport: 'G-12345678',
    school: 'No.4 middle school'
};

let {name, passport:id} = person;

console.log(name);  // 小明
console.log(id);    // G-12345678
// 注意: passport不是变量,而是为了让变量id获得passport属性:
console.log(passport);  // Uncaught ReferenceError: passport is not defined

 

解构赋值还可以使用默认值,这样就避免了不存在的属性返回undefined的问题:

var person = {
    name: '小明',
    age: 20,
    gender: 'male',
    passport: 'G-12345678'
};

// 如果person对象没有single属性,默认赋值为true:
var {name, single=true} = person;
name; // '小明'
single; // true

 

有些时候,如果变量已经被声明了,再次赋值的时候,正确的写法也会报语法错误:

// 声明变量:
var x, y;
// 解构赋值:
{x, y} = { name: '小明', x: 100, y: 200};
// 语法错误: Uncaught SyntaxError: Unexpected token =

这是因为JavaScript引擎把{开头的语句当作了块处理,于是=不再合法。解决方法是用小括号括起来:

({x, y} = { name: '小明', x: 100, y: 200});

 

解构赋值使用场景

解构赋值在很多时候可以大大简化代码。例如,交换两个变量xy的值,可以这么写,不再需要临时变量:

var x=1, y=2;
[x, y] = [y, x]

快速获取当前页面的域名和路径:

 

 使用解构赋值可以减少代码量,但是,需要在支持ES6解构赋值特性的现代浏览器中才能正常运行。目前支持解构赋值的浏览器包括Chrome,Firefox,Edge等。

 

2. 方法

在一个对象中绑定函数,称为这个对象的方法。

在 javascript 中,对象的定义是这样的:

var xiaoming = {
    name: '小明',
    birth: 1990
};

可以给对象 xiaoming 绑定一个函数,比如写一个 age 方法,返回 xiaoming 的年龄:

var xiaoming = {
    name: '小明',
    brith: 1990,
    age: function () {
        var y = new Date().getFullYear();
        return y - this.brith
    }
};

console.log(xiaoming.age());    // 29

 绑定到对象上的函数称为方法,和普通函数也没啥区别,这里使用了关键字:this

在一个方法内部,this是一个特殊变量,它始终指向当前对象,也就是xiaoming这个变量。所以,this.birth可以拿到xiaomingbirth属性。

如果分开来写,this就失效了。

function getAge() {
    var y = new Date().getFullYear();
    return y - this.birth;
}

var xiaoming = {
    name: '小明',
    birth: 1990,
    age: getAge
};

console.log(xiaoming.age());    // 29
console.log(getAge());  // NaN

 

注意这里 this的使用:

如果以对象的方法形式调用,比如 xiaoming.age(),该函数的 this 指向被调用的对象,也就是 xiaoming,这是符合我们预期的;

如果单独调用函数,比如 getAge(),此时,该函数的 this 指向全局对象,也就是 window

var fn = xiaoming.age; // 先拿到xiaoming的age函数
fn(); // NaN

这样是也不行的!要保证 this 指向正确,必须用 obj.xxx() 的形式调用!

 

apply

 要指定函数的this指向哪个对象,可以用函数本身的apply方法,它接收两个参数,第一个参数就是需要绑定的this变量,第二个参数是Array,表示函数本身的参数。

apply修复getAge()调用:

function getAge() {
    var y = new Date().getFullYear();
    return y - this.birth;
}

var xiaoming = {
    name: '小明',
    birth: 1990,
    age: getAge
};

console.log(xiaoming.age()); // 29
console.log(getAge.apply(xiaoming, [])); // 29  this指向xiaoming, 函数参数为空

 

另一个与 apply() 类似的方法是 call() ,唯一的区别是:

  apply() 把参数打包成 Array 再传入;

  call() 把参数按顺序传入.

比如调用Math.max(3, 5, 4),分别用apply()call()实现如下:

Math.max.apply(null, [3, 5, 4]); // 5
Math.max.call(null, 3, 5, 4); // 5

 对普通函数调用,我们通常把this绑定为null

 

3. 高阶函数

JavaScript的函数其实都指向某个变量。既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。

一个最简单的高阶函数:

function f(x, y, f) {
    return f(x) + f(y);
}

 当我们调用add(-5, 6, Math.abs)时,参数xyf分别接收-56和函数Math.abs,根据函数定义,我们可以推导计算过程为:

x = -5;
y = 6;
f = Math.abs;
f(x) + f(y) ==> Math.abs(-5) + Math.abs(6) ==> 11;
return 11;

 

3.1 高阶函数:map / reduce

(1)map()

map() 方法定义在 javascript 的 Array中,我们调用 Array的Map() 方法,传入自己的函数,就得到一个新的 Array作为结果:

 

map() 作为高阶函数,事实上它把运算规则抽象了,因此,可以使用map来做比较复杂的操作,比如把 Array的所有数字转为字符串:

 

(2)reduce()

Array 的 reduce() 把一个函数作用在这个 Array 的,这个函数必须接收两个参数,reduce() 把结果继续和序列的下一个元素做累积计算,其效果就是:

[x1, x2, x3, x4].reduce(f) = f(f(f(x1, x2), x3), x4)

例 -1  对一个 Array求和:

function sum(x, y) {
    return x+y;
}

console.log([1, 2, 3, 4, 5, 6].reduce(sum)); // 21

例 - 2 对一个 Array 求积:

function product(arr) {
    return arr.reduce(function (x, y) {
        return x *y;
    })
}

console.log(product([1,2,3,4,5])); // 结果 120

例 - 3 把[1, 3, 5, 7, 9]变换成整数13579

var arr = [1, 3, 5, 7, 9].reduce(function (x, y) { return x*10+y });
console.log(arr); // 结果: 13579

例 - 4 不要使用JavaScript内置的parseInt()函数,利用map和reduce操作实现一个string2int()函数:

function string2int(s) {
    return s.split('').map(function (x) {
        return +x;
    }).reduce(function (x, y) {
        return x*10+y;
    })
}

console.log(string2int('13579'));   // 结果: 13579

例 - 5 请把用户输入的不规范的英文名字,变为首字母大写,其他小写的规范名字。输入:['adam', 'LISA', 'barT'],输出:['Adam', 'Lisa', 'Bart']

function normalize(arr) {
    return arr.map(function (x) {
        return x[0].toUpperCase() + x.slice(1).toLowerCase();
    })
}

console.log(normalize(['adam', 'LISA', 'barT'])); // ["Adam", "Lisa", "Bart"]

例 - 6 Array 字符串 --> 数字   Array 数字 --> 字符串

// Array 元素字符串 转 数字
var arr = ['1', '2', '3'];
var r;
r = arr.map(Number);
console.log(r); // [1, 2, 3]


// Array 元素 数字 转 字符串
s = r.map(String);
console.log(s); // ["1", "2", "3"]

 

3.2 高阶函数: filter

filter也是一个常用的操作,它用于把Array的某些元素过滤掉,然后返回剩下的元素。

map()类似,Arrayfilter()也接收一个函数。和map()不同的是,filter()把传入的函数依次作用于每个元素,然后根据返回值是true还是false决定保留还是丢弃该元素。

例如,在一个 Array 中,删掉偶数,只保留奇数,可以这么写:

var arr = [1, 2, 4, 5, 6, 9, 10, 15];
var r = arr.filter(function (x) {
    return x % 2 !== 0  // 过滤条件,条件为true保留,为false剔除
});

console.log(r); // [1, 5, 9, 15]

把一个Array中的空字符串删掉,可以这么写:

var arr = ['A', '', 'B', null, undefined, 'C', '  '];
var r = arr.filter(function (s) {
    return s && s.trim(); // 注意:IE9以下的版本没有trim()方法
});
r; // ['A', 'B', 'C']

可见用filter()这个高阶函数,关键在于正确实现一个“筛选”函数。

利用 filter,可以去除 Array 的重复元素:

var
    r,
    arr = ['apple', 'strawberry', 'banana', 'pear', 'apple', 'orange', 'orange', 'strawberry'];

r = arr.filter(function (element, index, self) {
    return self.indexOf(element) === index;
});

console.log(r); // ["apple", "strawberry", "banana", "pear", "orange"]

去除重复元素依靠的是indexOf总是返回第一个元素的位置,后续的重复元素位置与indexOf返回的位置不相等,因此被filter滤掉了。

例 - 1 请尝试用filter()筛选出素数:

function get_primes(arr) {
    return arr.filter(
        x => {
            let result = true;
            let end =Math.sqrt(x);  // 开平方开出来是整数的要剔除掉
            let flag = 0;
            for (let i = 2; i <= end; i++) {    // 能被大于2且小于本身的整数整除的不是素数
                if (x % i === 0) {
                    flag = 1;
                    break;
                }
            }
            if (x === 1) {
                result = false;
            } else if ( flag === 0) {
                result = true;
            } else {
                result = false;
            }
            return result;
        })}

var
    x,
    r,
    arr = [];
for (x = 1; x < 100; x++) {
    arr.push(x);
}
r = get_primes(arr);
if (r.toString() === [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97].toString()) {
    console.log('测试通过!');
} else {
    console.log('测试失败: ' + r.toString());
}

 

3.3 高阶函数:sort()

JavaScript的Arraysort()方法就是用于排序的,但是排序结果可能让你大吃一惊:

// 看上去正常的结果:
['Google', 'Apple', 'Microsoft'].sort(); // ['Apple', 'Google', 'Microsoft'];

// apple排在了最后:
['Google', 'apple', 'Microsoft'].sort(); // ['Google', 'Microsoft", 'apple']

// 无法理解的结果:
[10, 20, 1, 2].sort(); // [1, 10, 2, 20]

第二个排序把apple排在了最后,是因为字符串根据ASCII码进行排序,而小写字母a的ASCII码在大写字母之后。

第三个排序结果是什么鬼?简单的数字排序都能错?

这是因为Arraysort()方法默认把所有元素先转换为String再排序,结果'10'排在了'2'的前面,因为字符'1'比字符'2'的ASCII码小。

sort()方法也是一个高阶函数,它还可以接收一个比较函数来实现自定义的排序。

 

比较数字类型:

 

要按数字大小排序,我们可以这么写:

var arr = [10, 20, 1, 2];
arr.sort(function (x, y) {
    if (x> y) {
        return 1;
    }
    if (x< y) {
        return -1;
    }
    return 0;
});

console.log(arr);   // 结果: [1, 2, 10, 20]

从大到小排序:

var arr = [10, 20, 1, 2];
arr.sort(function (x, y) {
    if (x> y) {
        return -1;
    }
    if (x< y) {
        return 1;
    }
    return 0;
});

console.log(arr);   // 结果: [20, 10, 2, 1]

 

比较字母类型:

var arr = ['Google', 'apple', 'Microsoft'];

arr.sort(function (s1, s2) {
    x1 = s1.toLowerCase();
    x2 = s2.toLowerCase();
    if (x1 < x2) {
        return -1;
    }
    if (x1 > x2) {
        return 1;
    }
    return 0;
});

console.log(arr);   // 结果:["apple", "Google", "Microsoft"]

 

注意:sort() 方法会直接对 Array 进行修改,它返回的结果仍然是当前 Array

 

Array 其他高阶函数介绍

对于数组,除了map()reducefilter()sort()这些方法可以传入一个函数外,Array对象还提供了很多非常实用的高阶函数。

every

every()方法可以判断数组的所有元素是否满足测试条件。

例如,给定一个包含若干字符串的数组,判断所有字符串是否满足指定的测试条件:

var arr = ['Apple', 'pear', 'orange'];
console.log(arr.every(function (s) {
    return s.length > 0;    // 因为每个元素长度都大于0,所以返回 true
}));

console.log(arr.every(function (s) {
    return s.toLowerCase() === s;   // 原元素不是每个都是小写,所以返回false
}));

 

注意:every 高阶函数,针对 Array 中每个元素进行检查,如果有一个不满足条件则返回 false。

 

find

find()方法用于查找符合条件的第一个元素,如果找到了,返回这个元素,否则,返回undefined

var arr = ['Apple', 'pear', 'orange'];
console.log(arr.find(function (s) {
    return s.toLowerCase() === s;   // pear 返回符合条件的第一个元素
}));

console.log(arr.find(function (s) {
    return s.toUpperCase() === s;   // undefined 因为没有任何一个元素满足条件,则返回 undefined
}));

 

findIndex

findIndex()find()类似,也是查找符合条件的第一个元素,不同之处在于findIndex()会返回这个元素的索引,如果没有找到,返回-1

var arr = ['Apple', 'pear', 'orange'];

console.log(arr.findIndex(function (s) {
    return s.toLowerCase() === s;   // 返回第一个符合条件元素的索引,没有则返回 -1
}));

console.log(arr.findIndex(function (s) {
    return s.toUpperCase() === s;   // 返回第一个符合条件元素的索引,没有则返回 -1
}));

 

3.4 闭包

函数作为返回值

高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回。

function lazy_sum(arr) {
    var sum = function () {
        return arr.reduce(function (x, y) {
            return x+y;
        })
    };
    return sum;
}

var f = lazy_sum([1,2,3,4,5]);
console.log(f); // 返回 function () { ... }
console.log(f());   // 返回结果: 15

在这个例子中,我们在函数lazy_sum中又定义了函数sum,并且,内部函数sum可以引用外部函数lazy_sum的参数和局部变量,当lazy_sum返回函数sum时,相关参数和变量都保存在返回的函数中,这种称为“闭包(Closure)”的程序结构拥有极大的威力。

请再注意一点,当我们调用lazy_sum()时,每次调用都会返回一个新的函数,即使传入相同的参数:

var f1 = lazy_sum([1, 2, 3, 4, 5]);
var f2 = lazy_sum([1, 2, 3, 4, 5]);
f1 === f2; // false

 f1()f2()的调用结果互不影响。

 

闭包

返回闭包时牢记的一点就是:返回函数不要引用任何循环变量,或者后续会发生变化的变量

 

3.5 箭头函数

ES6标准新增了一种新的函数:Arrow Function(箭头函数)。

具体写法:

var fn = x => x * x;
console.log(fn(10));    // 100

x => x * x;
等价于
function (x) { return x * x}

如果参数不是一个,就需要用括号()括起来:

// 两个参数:
(x, y) => x * x + y * y

// 无参数:
() => 3.14

// 可变参数:
(x, y, ...rest) => {
    var i, sum = x + y;
    for (i=0; i<rest.length; i++) {
        sum += rest[i];
    }
    return sum;
}

 

练习 1:

请使用箭头函数简化排序时传入的函数:

var arr = [10, 20, 1, 2];
arr.sort((x, y) => {
    return (x>y) ? 1:-1; // 在做 if 判断时,首先考虑能否使用 三元运算
});
console.log(arr); // [1, 2, 10, 20]

 

Guess you like

Origin www.cnblogs.com/hukey/p/10939708.html