JavaScript表单
javaScript 表单验证
HTML 表单验证可以通过 JavaScript 来完成。
数据验证
典型的数据验证有:
必需字段是否有输入?
用户是否输入了合法的数据?
在数字字段是否输入了文本?
HTML 约束验证
HTML 输入属性
CSS 伪类选择器
DOM 属性和方法
约束验证 HTML 输入属性
disabled 规定输入的元素不可用
max 规定输入元素的最大值
min 规定输入元素的最小值
pattern 规定输入元素值的模式
required 规定输入元素字段是必需的
type 规定输入元素的类型
约束验证 CSS 伪类选择器
选择器 描述
:disabled 选取属性为 “disabled” 属性的 input 元素
:invalid 选取无效的 input 元素
:optional 选择没有"required"属性的 input 元素
:required 选择有"required"属性的 input 元素
:valid 选取有效值的 input 元素
JavaScript 表单验证
验证表单数据是否为空?
验证输入是否是一个正确的email地址?
验证日期是否输入正确?
验证表单输入内容是否为数字型?
JavaScript 验证 API
1.获取表单
获取表单元素
以Document对象中forms属性来获取当前HTML页面所有表单集合
以Document对象中表单的name属性值来获取表单元元素
<body>
<form action="#">
<input type="submit">
</form>
<form name="mylove" action="#">
<input type="submit">
</form>
<script>
console.log(document.forms);
// 获取当前HTML页面所有表单元素
console.log(document.mylove);
// document表单名称-有些新浏览器是不支持
</script>
</body>
获取表单组件元素
以HTMLFormElement对象的elements属性来获取表单组件的集合
<body>
<form action="#">
<input type="text" name="username">
<input type="submit">
</form>
<script>
var form = document.forms[0];
console.log(form.elements);
</script>
</body>
2.表单操作
文本内容的选择
以HTMLElement对象和HTMLTextAreaElement对象中select()方法来获取文本框所有文本框的内容
<body>
<form action="#">
<input type="text" id="username" value="请输入你用户名">
<!---->
<input type="submit">
<!--定义提交按钮-->
</form>
<script>
var username = document.getElementById(username);
// 获取ID属性
username.addEventListener('focus',function(){
username.select();
})
username.addEventListener('select',function () {
console.log(username.selectionStart.username.selectionEnd);
var value = username.getAttribute('value');
var result = value.substring(username.selectionStart,username.selectionEnd);
console.log(result);
})
</script>
</body>
设置文本内容
在HTML5新增中setSelectionRange()方法,来获取一个焦点文本框的文本内容
<body>
<form action="#">
<input type="text" id="username" value="请输入你用户名">
<!---->
<input type="submit">
<!--定义提交按钮-->
</form>
<script>
var username = document.getElementById(username);
// 获取ID属性
username.addEventListener('focus',function(){
username.select();
})
username.addEventListener('select',function () {
console.log(username.selectionStart.username.selectionEnd);
var value = username.getAttribute('value');
var result = value.substring(username.selectionStart,username.selectionEnd);
console.log(result);
})
</script>
</body>
操作剪切板
以copy;cut,paste 来设置 操作剪切板的复制,剪切和粘贴
<body>
<form action="#">
<input type="text" id="username" value="请输入你用户名">
<input type="text" id="username1">
<input type="submit">
</form>
<script>
var username = document.getElementById('username');
username.addEventListener('copy',function (event) {
var data = event.clipboardData || window.clipboardData;
console.log(data);
console.log('这是复制操作');
var value = username.value;
var result = value.substring(selectionStart,username.selectionEnd);
console.log(result);
data.setData('text',result);
});
username.addEventListener('cut',function () {
console.log('这是个剪切操作');
});
var username1 = document.getElementById('username1');
username1.addEventListener('paste',function (event) {
event.preventDefault();
var data = event.clipboardData || window.clipboardData;
var result = data.getData('text');
/*得到DataTransfer对象
* geData()方法-获取数据内容*/
if (result === '用户名') {
result ='***';
}
username1.value = result;
})
</script>
</body>
下拉列表的操作
是以select和option对象来创建病提供一些属性和方法
<form action="#">
<select id="yx">
<option id="dj" value="dj">单机</option>
<option value="wy">网页</option>
<option value="dy">端游</option>
</select>
<select id="cyx1" multiple size="5">
<option value="dj">单机</option>
<option value="wy">网页</option>
<option value="dy">端游</option>
</select>
</form>
<script>
var yx = document.getElementById('yx');
// HTMLSelectElement对象
console.log(yx.length);
console.log(yx.options);
console.log(yx.selectedIndex);// 被选中<option>的索引值
// 属性
var yx1 = document.getElementById('yx1');
// size属性默认值是 0
console.log(yx1.size);
console.log(yx1.item(1));
yx1.remove(2);
var dj = document.getElementById('dj');
console.log(dj.index);
console.log(dj.selected);
console.log(dj.text);
console.log(dj.value);
</script>
3.表单验证
以checkValidity()如元素值不存在验证问题,会是true,如不是则返回false
以setCustomValidity(message)会为元素自定义个错误信息,如果设置了,该元素未无效,并显示
<body>
<form action="#">
<input type="text" id="username">
<input type="submit">
</form>
<script>
var username = document.getElementById('username');
username.addEventListener('blur',function () {
var value = username.value;
if (value === '' || value === undefined || vaiue === null) {
console.log('请输入你用户名');
}
});
</script>
</body>
4.表单提交
submit事件
以submit表示提交表单
<body>
<form action="#">
<input type="text" id="username">
<input type="submit">
</form>
<script>
var form = document.forms[0];
form.addEventListener('submit',function (event) {
console.log('该表单已被提交');
});
</script>
</body>
submit()方法
以submit表示提交表单,并用使用任意普通按钮即可完成提交
<body>
<form action="#">
<input type="text" id="username">
<input id="qyc" type="button" value="提交">
</form>
<script>
var qyc = document.getElementById('qyc');
qyc.addEventListener('click',function () {
var form = document.forms[0];
form.submit();//提交表单
});
</script>
</body>
JavaScript对象
在 JavaScript 中,几乎“所有事物”都是对象
- 布尔是对象(如果用 new 关键词定义)
- 数字是对象(如果用 new 关键词定义)
- 字符串是对象(如果用 new 关键词定义)
- 日期永远都是对象
- 算术永远都是对象
- 正则表达式永远都是对象
- 数组永远都是对象
- 函数永远都是对象
- 对象永远都是对象
所有 JavaScript 值,除了原始值,都是对象。
JavaScript原始值
原始值指的是没有属性或方法的值
原始数据类型指的是拥有原始值的数据
JavaScript定义了5种原始数据类型:
- string
- number
- boolean
- null
- undefined
原始值是一成不变的(它们是硬编码的,因此不能改变)
假设 x = 3.14,您能够改变 x 的值。但是您无法改变 3.14 的值
JavaScript对象是易变的
对象是易变的:它们通过引用来寻址,而非值。
如果 person 是一个对象,下面的语句不会创建 person 的副本:
var x = person; // 这不会创建 person 的副本。
对象 x 并非 person 的副本。它就是 person。x 和 person 是同一个对象。
对 x 的任何改变都将改变 person,因为 x 和 person 是相同的对象。
var person = {
firstName:"Bill", lastName:"Gates", age:62, eyeColor:"blue"}
var x = person;
x.age = 10; // 这将同时改变 both x.age 和 person.age
注意:JavaScript变量不是易变的,只有JavaScript对象如此
JavaScript对象属性
for…in
语句可以遍历对象的属性
for (variable in object) {
要执行的代码
}
var person = {
fname:"Bill", lname:"Gates", age:62};
for (x in person) {
txt += person[x];
}
删除属性
// delete关键词可以从对象中删除属性
var person = {
firstName:"Bill", lastName:"Gates", age:62, eyeColor:"blue"};
delete person.age; // 或 delete person["age"];
// delete关键词会同时删除属性的值和属性本身
JavaScript对象原型
原型继承
所有JavaScript对象都从原型继承属性和方法,例如:
日期对象继承自Date.prototype
,数组对象继承自Array.prototype
,Person对象继承自Person.protype
Object.prototype位于原型继承链的顶端:
日期对象、数组对象和 Person 对象都继承自 Object.prototype
JavaScript prototype
属性允许为对象构造器添加新属性和新方法:
function Person(first, last, age, eyecolor) {
this.firstName = first;
this.lastName = last;
this.age = age;
this.eyeColor = eyecolor;
}
// 添加新属性
Person.prototype.nationality = "English";
// 添加新方法
Person.prototype.name = function() {
return this.firstName + " " + this.lastName;
};
JavaScript函数
函数定义
一般函数定义的形式有以下几种:
- 函数声明法
function factorial(x) {
if(x <= 1) return 1;
return x * factorial(x - 1);
}
注意:函数声明语句”被提前”到脚本的顶部,所以可以在定义之前的代码调用函数。
- 函数赋值法(可定义为匿名函数)
var square = function(x) { return x*x; }
注意:变量的声明是可以提前的,但是给变量的赋值不会提前,所以这种方式的函数在定义之前无法调用。
- 函数定义后立即执行
var tensquared = (function(x) { return x*x; }(10));
注意: function左边的左括号是必需的,因为如果不写左括号,JavaScript解释器会将关键字function解析为函数声明语句。使用左括号后,JavaScript解释器才会正确地将其解析为函数定义表达式。
- 嵌套函数
function hypotenuse(a, b) {
function square(x) { return x*x; }
return Math.sqrt( square(a) + square(b) );
}
注意:嵌套函数中,注意作用域的使用。
函数调用
有4种方式可以调用JavaScript函数:
- 作为函数
- 作为方法
- 作为构造函数
- 通过call()和apply()方法间接调用
作为函数调用
函数调用就是直接通过function对象来调用函数。
var probability = factorial(5) / factorial(13);
根据ECMAScript3和非严格的ECMAScript5的规定,函数调用的上下文(this的值)是全局对象(window)。在ECMAScript 5的严格模式下,调用上下文是undefined。
作为方法调用
- 方法调用是通过对象的属性来调用函数。
- 方法调用和函数调用最大的一个区别是:调用上下文。方法调用的上下文是调用它的对象,可以通过this关键字引用该对象。
- this是一个关键字,不是变量,也不是属性名。JavaScript不允许给this赋值。
var calculator = {
operand1: 1,
operand2: 2,
add: function() {
this.result = this.operand1 + this.operand2;
}
};
calculator.add(); // 方法调用
calculator["add"](); // 另一种形式的方法调用
calculator.result; // 2,对象属性添加成功
- 如果嵌套函数作为函数调用,则this值不是全局对象就是undefined。
var o = {
m: function() {
var self = this;
console.log(this === o); // true
f(); // 作为函数调用
function f() {
console.log(this === o); // false
console.log(this === window); // true
}
}
}
- 如果嵌套函数作为方法调用,则this值指向调用它的对象。
var o = {};
function outer() {
var inner = function() {
console.log(this === o);
};
o.m = inner;
}
outer();
o.m(); // 输出: true,作为方法调用
作为构造函数调用
- 如果函数或方法调用之前带有关键字new,它就构成构造函数调用。
- 构造函数调用和普通的函数调用以及方法调用在实参处理、调用上下文和返回值方面都有不同。
- 构造函数调用会创建一个新的空对象,这个对象继承自构造函数的prototype属性。构造函数使用这个新创建的对象作为调用上下文,并可以使用this关键字引用这个新创建的对象。
var o = {
m: function() {
console.log(this === o);
}
};
o.m(); // true,方法调用
var s = new o.m(); // false,构造函数调用
间接调用
- JavaScript中的函数也是对象,函数对象也可以包含方法。其中的2个方法call()和apply()可以用来间接地调用函数。
- 两个方法允许显式指定调用所需的this值,也就是说,任何函数可以作为任何对象的方法来调用,哪怕这个函数不是那个对象的方法。
- 在ECMAScript3和非严格模式中,传入的null和undefined都会被全局对象代替,而其他原始值则会被相应的包装对象所替代。
f.call(o);
f.apply(o);
函数的实参和形参
JavaScript中的函数定义并未指定函数形参的类型,函数调用也未对传入的实参值做任何类型检查。实际上,JavaScript函数调用甚至不检查传入形参的个数。函数对于未传入的参数赋值为undefined,对于多余的参数忽略。
可选形参
由于调用函数传入的实参个数可以比形参个数少,所以函数应当对此有一个较好的适应性,给省略的参数赋一个合理的默认值。
function getPropertyNames(o, /* optional */ a) {
if (a === undefined) a = []; // 如果未定义,则使用新数组
for (var property in o)
a.push(property);
return a;
}
var a = getPropertyNames(o); // 将o的属性存储到一个新数组中
getPropertyNames(p, a); // 将p的属性追加到数组a中
其中第一个if判断语句可简写为:
a = a || [];
实参对象(arguments)
- 实参对象是一个类数组对象,可以通过数字下标访问传入函数的实参值,而不用非要通过形参名字来得到实参。
下面的函数可以接收任意数量的实参:
function max(/* ... */) {
var max = Number.NEGATIVE_INFINITY;
// 遍历实参,查找并记住最大值
for(var i = 0; i < arguments.length; i++)
if(arguments[i] > max) max = arguments[i];
return max;
}
- 在非严格模式下,实参对象的数组元素是函数形参所对应实参的别名,可以通过实参对象修改实参的值。
- 在非严格模式下,arguments仅仅是一个标识符,在严格模式中,它变成了一个保留字。严格模式中的函数无法使用arguments作为形参名或局部变量名,也不能给arguments赋值。
function f(x) {
console.log(x); // 输出实参的初始值
arguments[0] = null; // 修改实参数组元素同样会修改x的值
console.log(x); // 输出"null"
}
callee和caller属性
- 除了数组元素,实参对象还定义了callee和caller属性。
- callee属性指代当前正在执行的函数。caller指代调用当前正在执行的函数的函数。
在匿名函数中,可通过callee来递归地调用自身:
var factorial = function(x) {
if (x <= 1) return 1;
return x * arguments.callee(x-1);
}
将对象属性用做实参
当一个函数包含超过3个形参时,对于程序员说,要记住调用函数中实参的正确顺序比较困难。最好通过名/值对的形式来传入参数,这样参数的顺序就无关紧要了。
function arraycopy(/* array */ from, /* array */ to, /* integer */ length) {
// 逻辑代码
}
// 使用对象当参数
function easycopy(args) {
arraycopy(args.from, args.to, args.length);
}
var a = [1,2,3,4], b = [];
easycopy({ from: a, to: b, length: 4});
实参类型校验
JavaScript会在必要的时候进行类型转换,如果期望的实参是一个字符串,那么实参值无论是原始值还是对象都可以很容易地转换成字符串。但是当期望的实参是一个数组时,就无法对非数组对象进行转换了,所以有必要对实参进行校验。
function flexisum(a) {
var total = 0;
for (var i=0; i < arguments.length; i++) {
var element = arguments[i], n;
if (element == null) continue; // 忽略null和undefined实参
if (isArray(element))
n = flexisum.apply(this, element); // 如果是数组,递归计算累加和
else if (typeof element === "function")
n = Number(element()); // 如果是函数,调用它并做类型转换
else
n = Number(element);
if (isNaN(n))
throw Error("flexisum(): can't convert" + element + " to number.");
total += n;
}
return total;
}
作为值的函数
在JavaScript中,函数不仅是一种语法,也是值,也就是说,可以将函数赋值给变量,存储在对象的属性或数组的元素中,作为参数传入另外一个函数等。
// 声明一个函数
function square(x) { return x*x; }
var s = square; // 赋值给变量
s(4); // => 16
var o = { m: square }; // 赋值给对象
o.m(5); // => 25
var a = [square, 6]; // 赋值给数组
a[0](a[1]); // => 36
自定义函数的属性
函数是一种特殊的对象,所以可以为函数定义属性以完成特殊的需求。
可以通过函数属性实现”静态”变量的需求。
// 由于函数声明被提前了,因此可以在声明之前赋值
uniqueInteger.counter = 0;
function uniqueInteger() {
return uniqueInteger.counter++;
}
var a = uniqueInteger(); // 0
a = uniqueInteger(); // 1
a = uniqueInteger(); // 2
作为命名空间的函数
不在任何函数内声明的变量是全局变量,在整个JavaScript程序中都是可见的。基于这个原因,我们常常简单地定义一个函数用做临时的命名空间,在这个命名空间内定义的变量都不会污染到全局命名空间。
function mymodule() {
// 这个模块所使用的所有变量都是局部变量
// 而不会污染全局命名空间
}
闭包
JavaScript也采用词法作用域,也就是说,函数的执行依赖于变量作用域,这个作用域是在函数定义时决定的,而不是调用时决定的。为了实现这种词法作用域,JavaScript函数对象的内部状态不仅包含函数的代码逻辑,还包含函数定义时的作用域链。
函数体内部的变量都可以保存在函数作用域内,看起来是函数将变量”包裹”起来了,这种特性称为”闭包”。
var scope = "global scope";
function checkscope() {
var scope = "local scope";
// 定义时使用局部变量
function f() { return scope; }
return f(); // 返回函数的调用结果
}
checkscope(); // => "local scope"
如果更改下checkscope定义,将返回值更改为函数定义,如下:
var scope = "global scope";
function checkscope() {
var scope = "local scope";
// 定义时使用局部变量
function f() { return scope; }
return f; // 返回函数的定义
}
checkscope()(); // => "local scope"
虽然调用函数的作用域变了,但是函数的输出结果依然不变,因为函数保存了自己的作用域链。
通过闭包,可以实现一个更好的计数器类,将变量包裹起来:
function counter() {
var n = 0;
return {
count: function() { return n++; }
reset: function() { n = 0; }
};
}
var c = counter();
c.count(); // => 0
c.count(); // => 1
c.reset(); // => 0
函数属性、方法和构造函数
length属性
arguments.length表示传入函数的实参的个数。函数的length属性表示函数形参的个数,这个属性是只读的。
可以通过这个属性对函数的参数个数进行校验:
function check(args) {
var actual = args.length;
var expected = args.callee.length; // 形参个数
if (actual != expected) {
throw Error("Expected " + expected + "args; got " + actual);
}
}
prototype属性
这个属性指向一个对象的引用,这个对象称做”原型对象”。每一个函数都包含不同的原型对象。当将函数用做构造函数的时候,新创建的对象会从原型对象上继承属性。
call()和apply()方法
这2个方法属于函数的方法属性,在前面已经有介绍,不再重复介绍。
bind()方法
bind()是在ECMAScript5中新增的方法,这个方法的主要作用就是返回一个新的函数,这个函数将bind的对象作为调用上下文。
function f(y) { return this.x + y; }
var o = { x: 1 };
var g = f.bind(o); // bind返回一个函数
g(2); // =>1,以对象o作为调用上下文执行f(y)
可以通过如下代码来实现简单的bind():
function bind(f, o) {
if (f.bind) return f.bind(o);
else return function() {
// 此处的arguments为调用bind返回函数时传递的参数
// 上例中为2(g(2))
return f.apply(o, arguments);
}
}
但ECMAScript5中的bind()方法不仅仅是将函数绑定至一个对象,它还能将实参也绑定至this,这种编程技术, 有时被称为”柯里化“(currying)。参照下面的例子:
function f(y,z) { return this.x + y + z; };
var g = f.bind({x: 1}, 2); // 绑定this和y
g(3); // =>6,this.x绑定到1,y绑定到2,z绑定到3
下面的代码给出了更标准的bind()方法,将这个方法另存为Function.prototye.bind:
if( !Function.prototye.bind) {
Function.prototye.bind = function(o /*, args */) {
// 将this和arguments的值保存在变量中
// 以便在后面嵌套的函数中使用
var self = this, boundArgs = arguments;
// bind()返回一个函数
return function() {
// 创建一个实参列表,保存传入的所有实参
var args = [], i;
for(i = 1; i < boundArgs.length; i++) args.push(boundArgs[i]);
for(i = 0; i < arguments.length; i++) args.push(arguments[i]);
// 以绑定对象o作为上下文来调用函数self
// 并传递所有的实参args
return self.apply(o, args);
};
};
}
ECMAScript5定义的bind()方法有一些特性是上述代码无法模拟的:
- 真正的bind()方法返回的函数,length属性是绑定函数的形参个数减去绑定的实参个数。
- 真正的bind()方法可以创建构造函数,如果bind()返回的函数用做构造函数,将忽略bind()传入的this,但是实参会正常绑定。
- 由bind()方法返回的函数并不包含prototype属性,如果返回函数用做构造函数,则创建的对象从原始的未绑定构造函数中继承prototype,同时,使用instanceof运算符,绑定构造函数和未绑定构造函数并无两样。
function Point(x, y) {
this.x = x;
this.y = y;
}
Point.prototype.toString = function() {
return this.x + ',' + this.y;
};
var p = new Point(1, 2);
p.toString(); // '1,2'
var emptyObj = {};
var YAxisPoint = Point.bind(emptyObj, 0/*x*/);
// 以下这行代码在 polyfill 不支持,
// 在原生的bind方法运行没问题:
//(译注:polyfill的bind方法如果加上把bind的第一个参数,即新绑定的this执行Object()来包装为对象,Object(null)则是{},那么也可以支持)
// var YAxisPoint = Point.bind(null, 0/*x*/);
// 绑定函数的length属性 = 形参个数 - 绑定实参个数
console.log(YAxisPoint.length); // =>1,(2-1)
// 绑定函数不包含prototype属性
console.log(Point.prototype); // "Point { toString-function()}"
console.log(YAxisPoint.prototype); // undefined
// 绑定函数用做构造函数
// this指代新创建的对象
var axisPointA = new YAxisPoint();
console.log(axisPointA.toString()); // '0,undefined'
var axisPoint = new YAxisPoint(5);
console.log(axisPoint.toString()); // '0,5'
// 使用instanceof时,绑定构造函数和未绑定构造函数并无两样
console.log(axisPoint instanceof Point); // true
console.log(axisPoint instanceof YAxisPoint); // true
console.log(new Point(17, 42) instanceof YAxisPoint); // true
toString()方法
和所有的JavaScript对象一样,函数也有toString()方法。实际上,大多数(非全部)的toString()方法的实现都返回函数的完整源码。内置函数往往返回一个类似”[native code]”的字符串。
Function()构造函数
前面已经介绍,函数可以通过定义语句或直接量表达式来定义。函数还可以通过Function()构造函数来定义。
Function()构造函数可以传入任意数量的实参,最后一个实参表示的是函数体。
Function()构造函数并不需要通过传入实参以指定函数名。Function()会构造一个匿名函数。
var f = new Function("x", "y", "return x*y;");
// 这个定义与下面的函数定义等价
var f = function(x, y) { return x*y; }
Function()构造函数有以下几个特点:
- Function()在运行时动态地创建并编译函数。
- 每次调用Function()构造函数都会解析函数体,并创建新的函数对象。
- Function()构造函数创建的函数并不使用词法作用域,函数体代码的编译总是在顶层函数执行。
var scope = "global";
function constructFunction() {
var scope = "local";
return new Function("return scope"); // 无法捕获局部作用域
}
constructFunction()(); // => "global"
可调用对象(callable object)
可调用对象是一个对象,可以在函数调用表达式中调用这个对象。所有的函数都是可调用的,但并非所有的可调用对象都是函数。
- IE8之前的版本实现了客户端方法(诸如window.alert()和Document.getElementsById()),使用了可调用的宿主对象,而不是内置函数对象。IE9将它们实现为真正的函数,因此此类可调用的对象越来越罕见。
- 另外一个常见的可调用对象是RegExp对象,但代码最好不要对可调用的RegExp对象有太多依赖,对RegExp执行typeof运算的结果并不统一,在有些浏览器中返回”function”,在有些返回”object”。
检测一个对象是否是真正的函数对象:
function isFunction() {
return Object.prototye.toString.call(x) === "[object Function]";
}
函数式编程
和Lisp、Haskell不同,JavaScript并非函数式编程语言,但可以像操控对象一样操控函数,也就是说,JavaScript中可以应用函数式编程技术。
使用函数处理数组
使用函数式编程,简洁地实现计算平均值、标准差:
// 首先定义2个函数对象
var sum = function(x,y) { return x+y; }
var square = function(x) { return x*x; }
// 使用函数式编程计算平均数、标准差
var data = [1,1,3,5,5];
// 计算平均数
var mean = data.reduce(sum) / data.length;
// 计算标准差
var deviations = data.map(function(x) { return x-mean; });
var stddev = Math.sqrt(deviations.map(square).reduce(sum) / (data.length-1));
高阶函数(higher-order function)
高阶函数就是操作函数的函数,它接收一个或多个函数作为参数,并返回一个新函数。
function mapper(f) {
return function(a) { return a.map(f); } // 注意: 此处没有对参数a进行数组验证
}
var increment = function(x) { return x+1; }
var incrementer = mapper(increment);
incrementer([1,2,3]); // => [2,3,4]
不完全函数(partial function)
不完全函数是一种函数变换技巧,即把一次完整的函数调用拆成多次函数调用,每次传入的实参都是完整实参的一部分,每个拆分开的函数叫做不完全函数,每次函数调用叫做不完全调用(partial application)。
// 实现一个工具函数,将类数组对象转换为真正的数组
function array(a, n) { return Array.prototye.slice.call(a, n || 0); }
// 将第1次调用的实参放在左侧
function partialLeft(f /* , ... */ ) {
var args = arguments;
return function() {
var a = array(args, 1); // 获取第1个参数之后所有的实参
a = a.concat(array(arguments));
return f.apply(this, a);
};
}
// 将第1次调用的实参放在右侧
function partialRight(f /* , ... */ ) {
var args = arguments;
return function() {
var a = array(arguments);
a = a.concat(array(args, 1));
return f.apply(this, a);
};
}
// 将第1次调用实参中的undefined值替换成第2次调用的实参
function partial(f /* , ... */ ) {
var args = arguments;
return function() {
var a = array(args, 1);
var i = 0, j = 0;
for(; i < a.length; i++) {
if(a[i] === undefined) a[i] = arguments[j++];
}
a = a.concat(array(arguments, j));
return f.apply(this, a);
};
}
// 这个函数带有3个参数
var f = function(x, y, z) { return x * (y - z); };
partialLeft(f, 2)(3, 4); // => -2 [2 * (3 - 4)]
partialRight(f, 2)(3, 4); // => 6; [3 * (4 -2)]
partial(f, undefined)(3, 4); // => -6; [3 * (2 - 4)]