关于函数
看到代码就开始答题,必错!!!声明提前!!!变量提升!!!
一、函数的 5 种声明(重点匿名,具名,箭头)
- 1、具名函数
function f(x,y){
return x+y
}
f.name // 'f'
var f5 = function f4(){ //f4的作用域是这个函数本身
}
console.log(f);//可访问
- 2、匿名函数
var f
f = function(x,y){ //f引用函数 f记录的是函数的地址,不是函数本身
return x+y
}
var f2 = f;//复制的函数地址,同时指向函数
f.name // 'f' ,他是匿名函数,但是他有name
f2.name // 'f'
- 3、具名函数赋值
var f
f = function f2(x,y){ return x+y }
f.name // 'f2'
console.log(f2) // undefined
- 4、window.Function
var f = new Function('x','y','return x+y')
f.name // "anonymous"
- 5、箭头函数
var fn6 = i=>i+1 //一个参数
var fn7 = (i,j)=>i+j //两个参数
var fn8 = (i,j)=>{console.log(1);return i+j;}//执行多条内容,用{括起来}
var f = (x,y) => {
return x+y
}
var sum = (x,y) => x+y
var n2 = n => n*n
这几种方法大体相同,只是this不同
二、如何调用函数
f.call(asThis, input1,input2)
其中 asThis 会被当做 this,[input1,input2] 会被当做 arguments
禁止使用 f(input1, input2)
三、作用域
- 按照语法树,就近原则
- 我们只能确定变量是哪个变量,但是不能确定变量的值
拿到代码直接做——必然会错。请先提升声明!!!(只提声明,不带值)
词法作用域(静态作用域)练习:
var global1 = 1
function fn1(param1){
var local1 = 'local1'
var local2 = 'local2')
function fn2(param2){
var local2 = 'inner local2'
console.log(local1)
console.log(local2)
}
function fn3(){
var local2 = 'fn3 local2'
fn2(local2)
}
}
词法树.png
词法树只用来分析语义,和值没有关系
面试题1
var aa = 1;
function f(){
console.log(aa)
}
aa = 2;
f.call()
var a = 1
function f1(){
alert(a) // 是多少
var a = 2
}
f1.call()
答案:
var a = 1
function f1(){
var a //声明提前
alert(a) // undefined
a = 2
}
f1.call()
面试题2
var a = 1
function f1(){
var a = 2
f2.call()
}
function f2(){
console.log(a) // 是多少
}
f1.call()
答案:
var a = 1
function f1(){
var a = 2 //a=2只在当前作用域(f1)内有效
f2.call()
}
function f2(){
console.log(a) //1,拿的是全局a = 1
}
f1.call()
面试题3
var liTags = document.querySelectorAll('li')
for(var i = 0; i<liTags.length; i++){
liTags[i].onclick = function(){
console.log(i) // 点击第3个 li 时,打印 2 还是打印 6?
}
}
深入阅读:
四、什么是 call stack
stack堆:先进后出
队列:先进先出
浏览器不是先运行代码,而是先把申明提到前面
图片.png
五、this 和 arguments
-
重要:this 就是 call 第第一个参数!call 的其他参数统称为 arguments
-
this是隐藏的第一个参数,且必须是对象
function f(){
console.log(this)
console.log(arguments)
}
f.call() // window
f.call({name:'frank'}) // {name: 'frank'}, []
f.call({name:'frank'},1) // {name: 'frank'}, [1]
f.call({name:'frank'},1,2) // {name: 'frank'}, [1,2]
不传this,默认window
不传arguments,默认是空数组[]
为什么用call
,不用f()
直接调,用f()
是阉割版的f.call()
,就不容易分辨出this代表是什么
function f(){
'use strict'
console.log(this)
console.log(arguments)
return undefined
}
f.call();//需传入this和arguments([]),如果没有传,window不给this,就默认是undefined,也就是window(小写window,google浏览器打印出来是Window,所以运行结果是window,[]
f.call(1,2,3) // this 为 1,arguments 为 [2,3]
-
this为什么必须是对象
因为 this 就是函数与对象之间的羁绊
var person = {
name: 'frank',
sayHi: function(person){
console.log('Hi, I am' + person.name)
},
sayBye: function(person){
console.log('Bye, I am' + person.name)
},
say: function(person, word){
console.log(word + ', I am' + person.name)
}
}
person.sayHi(person)
person.sayBye(person)
person.say(person, 'How are you')
// 能不能变成
person.sayHi()
person.sayBye()
person.say('How are you')
// 那么源代码就要改了
var person = {
name: 'frank',
sayHi: function(){
console.log('Hi, I am' + this.name)
},
sayBye: function(){
console.log('Bye, I am' + this.name)
},
say: function(word){
console.log(word + ', I am' + this.name)
}
}
// 如果你不想吃语法糖
person.sayHi.call(person)
person.sayBye.call(person)
person.say.call(person, 'How are you')
// 还是回到那句话:this 是 call 的第一个参数
// this 是参数,所以,只有在调用的时候才能确定
person.sayHi.call({name:'haha'}) // 这时 sayHi 里面的 this 就不是 person 了
// this 真的很不靠谱
// 新手疑惑的两种写法
var fn = person.sayHi
person.sayHi() // this === person
fn() // this === window
sum(x,y){
return x+y
}
sum.call(undefined,1,2);//undefined必须写
六、call / apply
n.call(asThis, p1,p2) 是函数的正常调用方式
当你不确定参数的个数时,就使用 apply
fn.apply(asThis, params)
function sum(){
var n = 0;
for(var i = 0;i<arguments.length;i++){
n+= arguments[i]
}
return n
}
sum(1,2,3,4,5,6);//21
//若调用为一个数组
var a = [1,2,3,4,5,6]
sum.call(undefined,a[0],a[1],a[2]...)
更新用apply:
sum.apply(undefined,a);//21
七、bind
call 和 apply 是直接调用函数,而 bind 则是返回一个新函数(并没有调用原来的函数),这个新函数会 call 原来的函数,call 的参数由你指定。
八、return
每个函数都有 return
如果你不写 return,就相当于写了 return undefined
九、柯里化 / 高阶函数
返回函数的函数
* 柯里化:将 f(x,y) 变成 f(x=1)(y) 或 f(y=1)x
//柯里化之前
function sum(x,y){
return x+y
}
//柯里化之后
function addOne(y){
return sum(1, y)
}
//柯里化之前
function Handlebar(template, data){
return template.replace('{{name}}', data.name)
}
//柯里化之后
function Handlebar(template){
return function(data){
return template.replace('{{name}}', data.name)
}
}
柯里化可以将真实计算拖延到最后再做
关于柯里化的高级文章:
* 高阶函数:
在数学和计算机科学中,高阶函数是至少满足下列一个条件的函数:
a、 接受一个或多个函数作为输入:forEach、sort、 map、 filter、reduce
b、输出一个新函数:bind、lodash.curry
fn.bind.call(fn,{},1,2,3)
//
c、不过它也可以同时满足两个条件:Function.prototype.bind
[高阶函数的应用区别]https://blog.csdn.net/kingan123/article/details/79818566
偶数相加求和
array = [1,2,3,4,5,6,7,8]
var sum = 0;
for(var i= 0;i<array.length;i++){
if(array[i] % 2 ===0){
sum+=array[i]
}
}
console.log(sum)
法一:array.filter(function(n){n%2===0});//[2,4,6,8]
.reduce(function(prev,next){return prev+next})
法二:reduce(filter(array,function(n){n%2===0}),function(prev,next){return prev+next},0)
取奇数从小到大排序
array1 = [3,2,1,4,5,6,7,8]
sort(filter(array1,function(n){n%2===1}),function(a,b){return a-b})
接收一个函数,叫高阶函数
返回一个函数,叫高阶函数
函数被当作参数,叫回调
函数返回的函数参数比原函数少一个参数叫柯里化
十、回调
名词形式:被当做参数的函数就是回调
动词形式:调用这个回调
注意回调跟异步没有任何关系
十一、构造函数
返回对象的函数就是构造函数
一般首字母大写
new Number(1)//Number { 1 }
new String(1)
function Empty(){
this.name = '空'
return this //可不写
}
var empty = new Empty;//写new,自动返回 Empty.call({})
十二、箭头函数
箭头函数没有this,
setTimeout(function(){
console.log(this);//{name:'jack'}
}.bind({name:'jack'}),1000)
setTimeout(function(){
console.log(this);//window
},1000)
setTimeout(function(){
console.log(this);//{name:'jack'}
setTimeout(function(){
console.log(this);//window
},1000)
}.bind({name:'jack'}),1000)
setTimeout(function(){
console.log(this);//{name:'jack'}
setTimeout(function(){
console.log(this);//{name:'jack'}
}.bind(this),1000)
}.bind({name:'jack'}),1000)
//箭头函数
setTimeout(function(){
console.log(this);//{name:'jack'}
setTimeout((a)=>console.log(this),1000);//{name:'jack'}
}.bind({name:'jack'}),1000)
十三、闭包
如果一个函数,使用了他范围外的值,那么(这个函数+这个变量)就叫做闭包
var a = 1;
function f(){
console.log(a)
}