函数
- 函数声明,函数表达式
- 匿名函数
- 函数参数
- 作用域,闭包,this
- apply,call, bind
- 关于异步回调
- 函数的动态构建
- 函数式编程
如何封装好的函数
函数声明和函数表达式
- 函数声明会被提升hoisting
console.log([typeof add, typeof sub]); // ["function", "undefined"]
function add(x, y) {
return x + y;
}
var sub = function(x, y){
return x - y;
}
console.log([add(1, 2), sub(1, 2)]); // [3, -1]
箭头函数表达式
let double = x => x * 2;
let add = (x, y) => {
return x + y;
}
console.log([double(21), add(1, 2)]); //[42, 3]
匿名函数
在函数表达式和回调函数汇总常见
匿名函数与arguments.callee
function countDown(count, interval, callback) {
if (count <= 0) return;
callback(count);
setTimeout(function() {
if (--count > 0) {
setTimeout(arguments.callee, interval);
}
callback(count);
}, interval)
}
countDown(10, 1000, t => console.log(t));
//arguments.callee 指向当前函数本身
推荐使用具体名函数
function countDown(count, interval, callback) {
if (count <= 0) return;
callback(count);
setTimeout(function update() {
if (--count > 0) {
setTimeout(update, interval);
}
callback(count);
}, interval)
}
countDown(10, 1000, t => console.log(t));
函数的参数
- 具名的参数, function.length
- 可变参数与arguments
- ES6 rest 参数
- 参数默认值
function.length
// 函数过程抽象的方法 与传入的函数没有任何关系 都会做相同的处理
function __matchArgs__(fn) {
return function(...args) {
if (args.length !== fn.length) {
throw RangeError('Arguments not __matchArgs__');
}
return fn.apply(this, args);
}
}
var add = (a, b, c) => a + b + c;
var add = __matchArgs__((a, b, c) => a + b + c);
console.log(add(1, 2, 3));
console.log(add(4, 5));
可变参数与arguments
function add() {
// Array.from 方法 把类数组的对象转换为数组
// var args = [].slice.call(arguments);
let args = Array.from(arguments);
return args.reduce((a, b) => a + b);
}
console.log(add(1, 2, 3, 4)); //10
// JavaScript 函数涉及中经常会让参数允许有不同的类型
function setStyle(el, property, value) {
if (typeof el === 'string') {
el = document.querySelector(el);
}
if (typeof property === "object") {
for (var key in property) {
setStyle(el, key, property[key]);
}
} else {
el.style[property] = value;
}
}
console.log(setStyle.length);
setStyle('div', 'color', 'red');
setStyle('div', {
"fontSize": "32px",
"backgroundColor": 'white'
});
rest 参数
let add = (...args) => args.reduce((a, b) => a + b);
console.log(add(1, 2, 3, 4));
console.log(add.length);
function deepCopy(des, src) {
if (!src || typeof src !== 'object') {
return des;
}
for (var key in src) {
let obj = src[key]
if (obj && typeof obj === 'object') {
des[key] = des[key] || {}
deepCopy(des[key], obj);
} else {
des[key] = src[key];
}
}
return des;
}
function merge(des, ...objs) {
return [des, ...objs].reduce((a, b) => deepCopy(a, b))
}
console.log(merge({ x: 1 }, { y: 2 }, { z: 3 })) //{x: 1, y: 2, z: 3}
ES5 模拟rest 参数
// 函数变换
function __rest__(fn) {
var len = fn.length;
return function() {
var args = [].slice.call(arguments, 0, len - 1);
var rest = [].slice.call(arguments, length);
return fn.apply(this, args.concat([rest]));
}
}
var add = __rest__(function(args) {
return args.reduce(function(a, b) {
return a + b;
});
});
console.log(add(1, 2, 3, 4));
参数默认值
// 参数默认值
function addMessage(message) {
message = message || 'default message';
var el = document.createElement('p');
el.innerHTML = message;
document.body.appendChild(el);
}
addMessage(); // default message
addMessage('hello world'); // 'hello world
function addMessage2(message = 'default message') {
var el = document.createElement('p');
el.innerHTML = message;
document.body.appendChild(el);
}
addMessage2(); //default message
addMessage2('hello akira'); // hello akira
function add(x, y = 0) {
return x + y;
}
console.log(add(1)) ; //1
function add(x, y = 0) {
'use strict'
return x + y;
}
console.log(add()) ; //SyntaxError: Illegal 'use strict' directive in function with non-simple parameter list
作用域, 闭包 , this
- 重要: ES5用函数来构建作用域
- IIFE: 立即执行函数
- 什么是 ‘闭包’
- JavaScript 的’this’
for(var i = 0; i< 10; i++){
// (function (i) {
setTimeout(function(){
console.log(i); // 10 10 10 10 10 10 10 10 10 10
},100 * i)
// })(i)
}
for(let i =0; i < 10; i++){
setTimeout(function(){
console.log(i) // 0 1 2 3 4 5 6 7 8 9
}, 100 * i)
}
for(var i = 0; i< 10; i++){
(function (i) {
setTimeout(function(){
console.log(i); // 0 1 2 3 4 5 6 7 8 9
},100 * i)
})(i)
}
for(var i = 0; i < 10; i++){
setTimeout((function(i){
console.log(i); // 0 1 2 3 4 5 6 7 8 9
}).bind(null, i),100 * i);
}
闭包与私有数据
var MyClass = (function() {
var privateDate = 'privateDate';
function Class() {
this.publicDate = 'publicDate';
}
Class.prototype.getDate = function() {
return privateDate;
}
return Class;
})()
let Class;
{
let privateDate = 'privateDate';
Class = function() {
this.publicDate = 'publicDate';
}
Class.prototype.getDate = function() {
return privateDate;
};
};
var myObj = new Class();
console.log(myObj);
console.log([myObj.publicDate, myObj.privateDate, myObj.getDate()]); // ["publicDate", undefined, "privateDate"]
“this” 问题
JavaScript 的 this 是由函数求值时的调用者决定的!!
function Point2D(x, y) {
this.x = x;
this.y = y;
}
Point2D.prototype.showLength = function(){
var length = Math.sqrt(Math.pow(this.x, 2) + Math.pow(this.y, 2));
console.log(length);
}
// Async 异步
Point2D.prototype.showLengthAsync = function(){
var self = this;
setTimeout(function(){
self.showLength()
}, 1000);
}
var x = 30; y = 40;
var p = new Point2D(3, 4);
var f = Point2D.prototype.showLength;
f(); //50
setTimeout(p.showLength, 500); //50
p.showLengthAsync() //5
apply, call,bind
- 通过apply ,call 指定this调用函数
- ES5 的bind
- bind的重要意义和高级用法
call
function Poin2D(x, y) {
this.x = x;
this.y = y;
}
Poin2D.prototype.getLength = function() {
return Math.sqrt(Math.pow(this.x, 2) + Math.pow(this.y, 2));
}
var p = new Poin2D(1, 1);
console.log(p.getLength()); // 1.4142135623730951
var obj = { x: 3, y: 4 };
console.log(p.getLength.call(obj)); // 5
function foo() {
var args = [].slice.call(arguments);
console.log(Array.isArray(arguments)); //false
console.log(Array.isArray(args)); //true
}
foo()
apply
// apply
function __reverseArgs__(fn) {
return function() {
var args = [].slice.call(arguments);
return fn.apply(this,args.reverse());
}
}
function foo() {
console.log(Array.from(arguments));
}
var foo2 = __reverseArgs__(foo);
foo2(1, 2, 3, 4);
call 与 bind
function add(x, y) {
return x + y;
}
console.log(add.call(null, 1, 2)); //3
console.log(add.apply(null, [1,2])); //3
console.log(add.bind(null, [1, 2])); // function
let add1 = add.bind(null, 1);//先传一个1 返回一个函数 等待第二个传递第二个参数传入
let add2 = add.bind(null, 1, 2); //如果是bind 返回一个函数 如果是call 立即执行
let add3 = add.call(null, 1, 2); //如果是call 立即执行
console.log(add1(2)) // bind 是部分调用的函数
console.log(add2());
console.log(add3);
function setBodyState(state) {
document.body.className = state;
}
setBodyState.call(null, 'state1');
setTimeout(setBodyState.bind(null, 'state2'), 1500)
// bind 支持异步绑定 call 是立即执行的
异步与回调函数
java 的异步
- Timer
- 事件
- 获取数据API
- Promise
- 其它异步模型
用timer 异步执行
<div id="ball">
</div>
#ball {
width: 50px;
height: 50px;
line-height: 50px;
text-align: center;
border-radius: 50%;
border: 1px solid red;
margin: 50px auto;
}
#ball:hover {
cursor: pointer;
}
#ball.warn {
animation: 1s 7s blink linear 3 forwards normal;
}
@keyframes blink {
0% {
background:#fff
}
50% {
background: #f00
}
100% {
background: #fff
}
}
const ball = document.getElementById('ball');
ball.addEventListener('click', function() {
var startTime = Date.now();
var tId = setInterval(function() {
var t = 10 - Math.round((Date.now() - startTime) / 1000);
ball.innerHTML = Math.max(t, 0);
if (t <= 0) clearInterval(tId);
ball.className = 'warn';
}, 1000)
});
动画专用
const brick = document.getElementById('brick');
brick.addEventListener('click', function() {
let startTime = Date.now();
let cycle = 2000;
requestAnimationFrame(function update() {
let p = (Date.now() - startTime) / cycle;
brick.style.transform = 'rotate(' + 360 * p + 'deg)';
requestAnimationFrame(update);
})
})
Promise
MDN Promise
setTimeout(function() {
console.log('step1')
setTimeout(function() {
console.log('step2')
setTimeout(function() {
console.log('step3')
}, 1500)
}, 1000)
}, 500)
function wait(time) {
return new Promise(resolve => {
setTimeout(resolve, time);
})
}
wait(5000).then(function() {
console.log('step4');
return wait(1000);
}).then(function() {
console.log('step5')
return wait(1500);
}).then(function() {
console.log('step6')
})
(async function() {
await wait(5000);
console.log('step7');
await wait(1000);
console.log('step8');
})()
其它异步模型
- 异步串行模型 through2 –gulp 使用
- 异步串行模型 connect – express koa 使用
- ES6 generators
- ES2015 async/wait
函数的动态创建
- Function 构造器与函数模版
- 过程抽象与高阶函数
用Function 处理不规范的JSON
// 处理不规范的json
// 处理不规范的json
var brokenJSON = `{
a: 1,
b: 2,
c: 'message'
}`
function parseDate(data) {
return (new Function('return' + data))();
}
try {
console.log(JSON.parser(brokenJSON));
}catch(ex){
console.log(ex.message);
console.log(parseDate(brokenJSON))
}
let add = new Function('x','y', 'return x+y');
console.log(add(1, 2));
抽象过程与函数式编程
- 过程抽象的定义
- 纯函数
- 等价函数
- 举例说明
- 过程抽象与函数式编程
过程抽象与数据抽象的区别
过程抽象与数据抽象的区别
define: f - > dataFromF = f(data)
define: g - > result = g(dataFromF)
// 数据抽象
define: f - > dataFromF = f(data);
define: g - > k = g(f), result = k(data);
// 过程抽象
纯函数
什么是纯函数?
一个函数如果输入参数确定, 如果输出结构是唯一确定的, 那么它就是纯函数
纯函数好处?
无状态 无副作用 幂等,无关时序
// 下面哪些是纯函数
// 第一个和最后一个是纯函数 其它都不是
function add(x, y) {
return x + y;
}
function random(min, max) {
return Math.floor(Math.random() * (max - min) + min);
}
let count = 0;
function addCount() {
count++;
}
function setBgColor(color) {
background.body.backgroundColor = color;
}
function __reduce__(fn) {
return function(...args) {
return args.reduce(a, b) => fn(a, b);
}
}
过程抽象提升函数纯度
function setColor(el, color) {
el.style.color = color;
}
function __multi__(fn) {
return function(arrayLike, ...args) {
return Array.from(arrayLike).map(item => fn(item, ...args));
}
}
function setColors(els, color) {
Array.from(els).map(el => setColor(el, color));
}
let setColors = __multi__(setColor);
setColors(document.querySelectorAll('#datalist > li'), 'red');
function add(x, y){
return x + y;
}
add = __multi__(add);
console.log(add([1,2], 3)) // [4, 5]
等价函数
function __equal__(fn) {
return function(...args){
return fn.apply(this, args);
}
}
function add(x, y) {
return x + y;
}
add = __equal__(add);
console.log(add(3,4)); //7
let obj = {
x: 1,
y: 2,
add: function(){
return this.x + this.y
}
}
let objAdd = __equal__(obj.add);
console.log(objAdd.call(obj)); //3
拦截和监控
function __watch__(fn) {
return function f(...args) {
if (f.before) {
f.before(this, args);
}
let ret = fn.apply(this, args);
if (f.after) {
f.after(this, ret, ...args);
}
return ret;
}
}
$ = __watch__($);
$.after = function(thisObj, retVal) {
if (retVal.css) {
retVal.css = __watch__(retVal.css);
retVal.css.before = function() {
console.log("不推荐使用 .css 建议使用className")
}
}
}
let el = $('#datalist > li');
el.css('color', 'red');
性能优化
function __watch__(fn) {
return function f(...args) {
let blocked = false;
if (f.before) {
blocked = f.before(this, ...args) === true;
}
if (!blocked) {
let ret = fn.apply(this, args);
if (f.after) {
f.after(this, ret, ...args);
}
return ret;
}
}
}
$ = __watch__($);
$.after = function(thisObj, retVal) {
if (retVal.css) {
let = _origin = retVal.css
retVal.css = __watch__(retVal.css);
retVal.css.before = function(target, ...agrs) {
requestAnimationFrame(() => {
// 用 requestAnimationFrame 优化性能
_origin.apply(target, agrs);
})
// 返回 true 组织默认行为
return true
}
}
}
let el = $('#datalist > li');
el.css('color', 'red');
假设 原始函数不支持 reduce
function add(x, y) {
return x + y;
}
function mul(x, y) {
return x * y;
}
function concat(arr1, arr2) {
return arr1.concat(arr2);
}
console.log(add(1, add(2, 3))) //6
console.log(mul(1, mul(2, mul(3, 4)))) //24
console.log(concat([1, 2], concat([3, 4], [5, 6]))) //[1, 2, 3, 4, 5, 6]
方案1 改写三个方法
function add(...args) {
return args.reduce((x, y) => x + y);
}
function mul(...args) {
return args.reduce((x, y) => x * y);
}
function concat(...args) {
return args.reduce((arr1, arr2) => arr1.concat(arr2));
}
console.log(add(1, 2, 3)) //6
console.log(mul(1, 2, 3, 4)) //24
console.log(concat([1, 2],[3, 4], [5, 6])) //[1, 2, 3, 4, 5, 6]
方案2:包装为reduce
function reduce(fn, ...args) {
return args.reduce(fn);
}
function add(x, y) {
return x + y;
}
function mul(x, y) {
return x * y;
}
function concat(arr1, arr2) {
return arr1.concat(arr2);
}
console.log(reduce(add, 1, 2, 3)) //6
console.log(reduce(mul, 1, 2, 3, 4)) //24
console.log(reduce(concat, [1, 2], [3, 4], [5, 6])) //[1, 2, 3, 4, 5, 6]
方案三 bind 一下
function reduce(fn, ...args) {
return args.reduce(fn);
}
function add(x, y) {
return x + y;
}
function mul(x, y) {
return x * y;
}
function concat(arr1, arr2) {
return arr1.concat(arr2);
}
add = reduce.bind(null, add);
mul = reduce.bind(null, mul);
concat = reduce.bind(null, concat)
console.log(add(1, 2, 3)) //6
console.log(mul(1, 2, 3, 4)) //24
console.log(concat([1, 2], [3, 4], [5, 6])) //[1, 2, 3, 4, 5, 6]
方案4 :函数变换(过程抽象)
function __reduce__(fn) {
return function(...args){
return args.reduce(fn.bind(this));
}
}
function add(x, y) {
return x + y;
}
function mul(x, y) {
return x * y;
}
function concat(arr1, arr2) {
return arr1.concat(arr2);
}
add = __reduce__(add);
mul = __reduce__(mul);
concat = __reduce__(concat);
console.log(add(1, 2, 3)) //6
console.log(mul(1, 2, 3, 4)) //24
console.log(concat([1, 2], [3, 4], [5, 6])) //[1, 2, 3, 4, 5, 6]
方案5 支持异步
function __reduce__(fn, async) {
if (async) {
return function(...args) {
return args.reduce((a, b) => {
return Promise.resolve(a).then((v) => fn.call(this, v, b));
});
}
} else {
return function(...args) {
return args.reduce(fn.bind(this))
}
}
}
function add(x, y) {
return x + y;
}
function mul(x, y) {
return x * y;
}
function concat(arr1, arr2) {
return arr1.concat(arr2);
}
function asyncAdd(x, y) {
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(`${x} + ${y} = ${x + y}`);
resolve(x + y);
}, 1000)
});
}
add = __reduce__(add);
mul = __reduce__(mul);
concat = __reduce__(concat);
console.log(add(1, 2, 3)) //6
console.log(mul(1, 2, 3, 4)) //24
console.log(concat([1, 2], [3, 4], [5, 6])) //[1, 2, 3, 4, 5, 6]
asyncAdd = __reduce__(asyncAdd, true);
asyncAdd(1, 2, 3, 4, 5, 6).then((v) => console.log(v));
函数异步化和串行执行
function __reduce__(...fnList) {
return function(...args) {
if (fnList.length <= 0) return;
fnList[0] = fnList[0].apply(this, args);
return fnList.reduce((a, b) => b.call(this, a));
}
}
function __pipe__(...fnList) {
return function(...args) {
var fn = fnList.reduceRight((a, b) => (...args) => b.apply(this, [...args, a]));
return fn.apply(this, args);
}
}
function add(x, y) {
return x + y;
}
function double(x) {
return 2 * x;
}
var foo = __reduce__(add, double, double, double);
console.log(foo(1, 2));
function taskA(x, next) {
console.log(`task a: ${x}`);
next()
}
function taskB(next) {
console.log('task b');
next()
}
function taskC() {
console.log('task c')
}
var foo2 = __pipe__(taskA, taskB, taskC);
foo2(10);
throttle 避免重复点击 节流
const btn = document.getElementById('btn');
function throttle(fn, wait) {
var timer;
return function(...args) {
if (!timer) {
timer = setTimeout(() => timer = null, wait);
return fn.apply(this, args);
}
}
}
// 按钮每500ms 一次点击有效
btn.onclick = throttle(function() {
console.log('button clicked')
}, 500)
debounce 防止重复点击
const btn = document.getElementById('btn');
function debounce(fn, delay) {
var timer = null;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
}
}
btn.onclick = debounce(function() {
console.log('button clicked')
}, 300)
multicast 批量操作DOM元素
function multicast(fn) {
return function(list, ...args) {
if (list && list.length != null) {
return Array.from(list).map((item) => fn.apply(this, [item, ...args]));
} else {
return fn.apply(this, [list, ...args]);
}
}
}
function setColor(el, color) {
return el.style.color = color;
}
setColor = multicast(setColor);
var list = document.querySelectorAll('li:nth-child(2n+1)');
setColor(list, 'red');
关于函数式编程的呢个(FP)
- 什么式函数式编程
函数式编程与前端代码有什么关系
- 函数的纯度和”提纯”
- Uncurrying Currying & Partial Application
阅读
- JavaScript 与函数式编程
- 函数式编程术语解析
- 什么式纯函数
- 函数式编程离我们还有多远
- 高阶函数对系统的”提纯”
计算机程序的构造与解释
如何封装好的函数
- 明确职责
- 限制副作用
- 过程的优化
掌握抽象度
总结
这一课 我们学习了什么?
函数的本质式 :封装过程 开放接口
理解过程抽象和函数式编程
Tip: 好的程序设计方式式面向接口编程, 而不是面向实现编程
阅读
MDN JavaScript 指南, JavaScript 参考文档
作业练习
- leetcode 刷题 :任意5道题目 记录解法心得
- 阅读jQuery 源码 ,模仿实现jQuery 链式调用和批量操作的API
- 前面的课程离讲过数据视图实现双向绑定的原理, 但没有封装,试着实现一个简单的数据视图双向绑定的封装
- 继续优化和完善你们自己做小网站功能