函数又叫方法。
学了一段时间JS了,首先,我要如何看待函数?
自己要建立什么观念呢?其实刚开始我本人的做事方法是一口吃个胖子,能一下写一秃噜代码,就一下写到自己所想的再前一步,当然在后面开发项目的时候,代码一写一长溜,后来就觉得不好看,代码太难看了,还有就是函数的复用了。
为什么要用函数?为什么?我经常看老师写代码,明明可以接着写下去的,结果呢,他直接把剩下的思路造了一个函数出来,他就说他喜欢这样,方便。单单方便一个词,这种思维就是一种把步骤封装起来,模块化的感觉,虽然刚开始我是觉得多此一举的,事实上,这是一种做事习惯,态度。事实证明,这样的习惯确实很好,代码漂亮了,其实思路更清晰了,这是关键。所以,我也要如此,有种写代码就要时刻准备着函数的品质的。
对函数本身应该如何看待呢?它是一段命令,构成里面有参数。第一点,就是参数的研究,逻辑上涉及到了传参,赋值,作用域,声明前置但赋值不前置,作用域链,甚至是原型与原型链的那个this。第二点,这个函数要执行的话,在时机上是不是同步的,后续的代码执行要不要必须在它完成之后再执行?比如定时器,node.js的读写文件,上次写无限轮播时的animate的用法,事件处理,还有ajax发请求要数据,数据到了做什么,常识就是回调函数,当然多了就成回调地狱了,后续会有更多的写法,如Promise。
函数的声明,调用
function functionName(){} //声明
functionName() //调用,代码复用
var a = new sum() //构造函数,原型对象那里的知识的声明
var a = function(){} //函数表达式,跟声明是有区别的
函数的参数
调用的时候,会传递进去参数的,所有函数内部都有个arguments对象,类数组对象,arguments[0],就是第一个参数。
function name(a){
console.log(a)
}
等同于
function name(){
var a = arguments[0]
console.log(a)
}
参数传多了,多的就是undefined,少了就是默认的从左到右传进去。
强制限制参数数量:
function(x,y){
if(arguments.length !==2){...}
}
设置默认参数,其实在上次写Jquary插件的时候,就用到了,那个动画效果中的。
function(x,y){
x = x || 0
y = y || 1
return [x,y]
}
把这个arguments转化为数组,方法:
function toArr(arguments){
return [].slice.call(arguments) //原型的this的知识
}
没有重载
没有重载,一个函数就是一个函数,再写一遍就覆盖了,如何传递不同参数?
只能在函数内部用if判断参数是哪一个参数,再相应地做事。
函数返回值
跟console.log()区别开,console.log()也是一个函数,这个函数值是undifined。只是把结果展示给控制台。
如果写一个函数没有return,这个函数值就是undifined。
return 会终止这个函数运行,我在项目里加锁经常用的。
声明前置
废话不多说,声明的变量,函数,都会提前,提前到哪里?到它所在的块级元素的最前面,全局作用域下,比较好说,提前到最前面。如果是函数内,就是在整个函数内的最前面。
这里跟后面要学的封装呼应了,想来十几天前刚学封装的时候,经常出错,就是因为在封装的对象里,声明的变量落后于执行它的函数,因为它们不在一个范畴了哦。因为一个this惹的祸。
然后,我要说的点是,只是声明前置,赋值依旧在运行到它所在的那行代码的时候,值才成立的。
console,log(a) //undifined 而不是报错,也不是3
var a = 3
function foo(){
console.log(tmp) //undifined 变量已声明前置
var x > -3
if(x < 0){ //if之流都是语句,不是函数。这个语句有个块内部环境,
//但是函数内变量作用域是整个函数。
var tmp = -x
}
console.log(tmp) //3
}
fn() //报错了
var fn = function(){
console.log('fn,,,')
}
等同于
var fn
fn() //fn不是一个函数
fn=function(){
console.log('fn,,,')
}
命名冲突
原理就是声明前置🏠后面的声明赋值覆盖。
var fn = 3
function fn(){}
console.log(fn) //3
等同于
var fn
function fn(){}
fn = 3
console.loog(fn)
function fn(fn){
console.log(fn) //相当于前面有个 var fn = arguments[0] = 10
var fn = 3
console.log(fn)
}
fn (10) // 10 3
递归
自己调用自己,
比如阶乘。
效率很低,思路简单,逻辑清晰。
function fact(n){
if(n ===1){
return 1
}
return n*fact(n-1)
}
作用域与上下文
首先,作用域的研究对象是函数里的变量有意义的地方,那一块叫作用域。而上下文的研究对象是调用当前代码的对象的引用,也就是那个恶心的this。一个是变量作为研究时出现的,一个是这行代码对应的对象this作为研究时出现的。十几天前第一次讲原型对象时,我错误地认为作用域就是this,但是作用域还有作用域链,又感觉怪怪的,而且没有this的属性方法。
作用域
一提它,就想变量。
全局的,就是声明在全局,局部的就是声明在函数体内。
全局变量一直存在,随时调用,修改。
局部的变量只在局部有作用,跟局部外一点关系都木有,也不会影响到外部的其他代码,也不会被外部所知道。每次的存在意义就是在调用函数时才有,而且每次都有不同的作用域,期间只能在作用域内赋值,求值,对值,不能访问作用域外的值。
JS里{}没有带来块级作用域。但ES6里有let,所以函数里是会有的。
if,for循环之类的语句等,定义的变量都是全局的,外边可以访问,这就是块级作用域在JS中无效,有效的话,就不能被语句外访问到定义的变量了。在函数里的话就是函数的全局变量了。
立刻执行的函数表达式
这是模拟块级作用域的实例,也是闭包的应用。
JS 中,一个函数定义的变量,函数执行后,变量会被销毁的,这样,后续再访问其中的变量就访问不到了。
(function(){})() //对,括号部分相当于变量了,阻止被解析成声明
function(){}() //错
var a =function(){}
a() //对
this上下文
上下文,就是一个函数被new出来时候,或者函数是一个对象的属性或者方法的形式时,反正就不是函数表达式,也不是字面量形式,哎,这时候this就出现了。就是有对象这种类型参与时,上下文就出来了。this是什么东东?
this是调用这个函数时,这个函数所属的对象。
var obj = {
haha:function(){
console.log(this === obj)
}
}
obj.haha() //true
全局变量都是window这个对象的属性。
作用域链
JS是单线程语言,在浏览器同时,只能做一件事。初始,默认作用域是全局window。执行函数,先创建它的作用域,是把函数作用域拉到作用域链的顶部,执行。执行完了,就移除,回到以前的作用域,以前的作用域可以是它父亲的作用域,更外边最外边就是全局。
每执行一个函数,就出在一个新作用域下。
作用域链的根本就是,从自己内部找变量,找不到就攀爬作用域链,从自己函数所声明的作用域找,找不到,从自己函数所在作用域代表的函数的作用域找,,,一层一层翻。还有一个注意点,声明前置。
练一练:
var a =1
function fn1(){
function fn2(){
console.log(a) //找a,内部没有,从自己函数声明的那个作用域找
}
function fn3(){
var a = 4
fn2()
} //找fn2
var a = 2 //找到了,自身作用域有,没有找fn3里的。
return fn3
}
var fn = fn1() //找fn3
fn() //2
var a= 1
function fn1(){
function fn3(){
var a = 4
fn2()
}
var a =2
return fn3
}
function fn2(){
console.log(a)
}
var fn = fn1()
fn() //1 一个意思。fn2的作用域是全局。
这里调用一个函数fn,fn是全局变量,不会被删除,fn函数返回一个函数fn3,就生成一个闭包,保存了返回函数fn3的临时的变量。
闭包重新认知
当一个嵌套函数,在作用域外或者定义外被访问,让它可以在外部函数返回后被执行,就是闭包了。作用就是保存局部变量。
function ha(){
var a =1
return function say(){
return a
}
}
var haha = ha()
console.log(haha()) //1
保存变量对比:
这里让我想起了上次那个音乐主页做的封装的一个点了,因为要把这个主页分成两部分封装,但是两部分之间有个交互,需要从一个封装对象里传入另一个对象一些参数,并且保存。那里,直接在另一个封装对象里添加了一个属性,让这个属性的值等于参数,就保留了。不一样的想法了,,,
var man = (function (){
var a =1
function isman(x){
a += x
}
function iswoman(x){
a -= x
}
return{
sex:'',
sexmesold:function(x){
//do something
},
sexget:function(x){
isman(x)
}
}
})() //立刻执行的变种可以理解为把var obj =man(),obj(),直接一步达到,也不引入新变量obj了。
//私有属性和方法,变量,都在它整个生命周期保留着,通过man.sexget与外部交互,这个sexget自己需求随意创建的,符合你的预期就行。
保护man的全局命名空间,很有用,暴露的接口就是那个回调函数了。
call和apply
所有函数都有这两个方法,只是参数不同。
call函数需要传递参数列表,apply函数允许你传递参数为数组:
function ha(a,b,'c'){}
ha.call(window,a,b,'c')
ha.apply(window,[a,b,'c'])
//ha函数在window的作用域下执行,并提供了三个参数。为了复用函数,脱离它定义的作用域。只保留方法。
function ha(){
console.log(this.name)
}
function me(){
this.name = 'me'
ha.call(me)
}
me() //'me'
function sum(){
}
还有一个bind,还是在原型那部分再写吧。
参考了大神的认识javascript中的作用域和上下文