javascript 基础之 - 函数
函数乃 javascript 中一等公民. 它和变量一样, 可以作为参数 , 也可以作为返回值在 js 的世界里到处穿梭, 畅通无阻.
var fn = function(){}
typeof fn ; // 'function'
fn instanceof Function ; // true
Object.prototype.toString.call(fn) ; // "[object Function]"
函数创建方式#
// 1. 声明式
function test(){
console.log('this is a function')
}
// 2.表达式
var test2 = function(){
}
// 3.构造式
var test3 = new Function('a','b','return a+b')
// 4.匿名函数式
(function(){ console.log(1); })
// 5.箭头函数式
var fn = (a)=>{ console.log(a) } ;
// 6.bind 式
var fn1 = function(){ console.log(this.name) }
var fn2 = fn1.bind({name:'xx'}); // => bind 生成另一个函数返回
函数调用情景#
// 1. 普通执行
var fn = function(){} ;
fn();
// 2. 匿名函数自执行, 最前面的分号, 只是防止上一句末尾未加分号.
;(function(){ console.log('auto run'})();
// 3. call 和 apply 调用
fn.call({},a,b,c);
fn.apply({},[a,b,c]);
// 4. 绑定回调式
setTimeout(function(){},1000);
document.addEventListener('click',function(){});
函数传参#
- 默认参数
// 1. 常规方法
function test(a,b){
a = a || 1;
b = b || 1;
console.log(a,b)
}
// 2.ES6 新增
function test2(a=1,b=1){
console.log(a,b)
}
// 1 和 2 的区别
// ①
test(); // 1 1
test(null,undefined); // 1 1
test(0, false); // 1 1
test('', NaN); // 1 1
// ②,也就是默认的值只有在不传 或 传入 undefined 的时候生效
test2(); // 1 1
test2(undefined, null); // 1 null
test2(0, false); // 0 false
test2('', NaN); // NaN
// 3. 默认参数的妙用,默认参数为惰性求值, 已传值则不会执行默认参,有些鸡肋
function login(name=required('name'),password=required('password')){
}
function required(field){
console.log(`${field}字段不能为空`);
}
- 基本类型和引用类型的区别
// 1.基本类型, 传递为真实值, 相当拷贝一份传入函数
var a = 10
function test(num){
num += 10
}
test(a);
console.log(a); // 10
// 2.引用类型, 传递的为引用值. 在引用值上的操作都直接影响到原数据
var obj = {}
function test2(data){
data.name = 'xx'
}
test2(obj);
console.log(obj); // {name:'xx'}
- 函数中的变量提升
// 参数 a , 局部变量 a 时, 会发生什么?
function test(a){
a = 10;
var a;
console.log(a)
}
test(); // a 的变化 => undefined -> (a = 10) -> 10
test(1); // a 的变化 => 1 -> (a = 10) -> 10
// 参数 a, 局部变量 a , 局部函数 a
function test2(a){
a = 10 ;
var a ;
function a(){
console.log(a)
}
}
test(); // a 的变化 => function a -> (a = 10) -> 10
test(1); // a 的变化 => function a -> (a = 10) -> 10
test(function b(){}); // a 的变化 => function a -> (a = 10) -> 10
函数中的变量提升结论;
1. 一定不要这样写, 会被打的.
2. 基本规律和常规变量提升一样, var 和 function 两者声明提前, 此处加入了参数姑且叫 param, param 也可以看做是一个声明; 在函数预编译阶段他们的顺序是:
var -> param -> function, 其中 var 声明永远是返回 undefined , param 和 function 则为实际值, 局部 function 声明 会覆盖 param 的声明.
函数自带 buff#
- name ; 函数的名称
// 字面量式
var fn = function b(){}
fn.name ; // 'b'
var fn2 = function(){};
fn2.name ; // 'fn2'
// 声明式
function c(){};
c.name; // 'c'
// 匿名函数式
(function(){}).name; // ''
(function b(){}).name; // 'b'
// 箭头函数
((a,b)=>{}).name; // ''
((a,b)=>{}).length; // 2
var fn3 = (a,b)=>{};
fn3.name ; // 'fn3'
普通函数定义三要素;
1. function 表示这是一个函数声明
2. () 括号中可以放参数, 即函数的参数
3. {} 花括号中放逻辑代码
总结:
函数的
name
属性在function
后面若有值, 则以此值为准;
若没有值, 则是空字符串, 若将该函数赋值给一个变量, 那name
值就是该变量名称.
- length ; 参数的个数
- arguments
函数未执行时 , arguments 为null
function test(a,b){
console.log(arguments);
console.log(arguments instanceof Array); // false
console.log(Object.prototype.toString.call(arguments)); // "[object Arguments]"
}
// arguments => null
test(1,2);
// 执行时 arugments=> Arguments(2) [1, 2, callee:
ƒ test(a,b), length:2 ,Symbol(Symbol.iterator): ƒ]
// 从下图也可以看出, 在执行 test 的时候, local 变量中的 arguments 和 function 上的 arguments 看似一样, 但两者却是不相等的.
// 所以 function 上的 arguments 的作用尚未可知
// 也许是在需要 arguments 变量时, 从 function 上拷贝了一份.
// arguments1 => callee 指向函数本身, 所以递归也可以这样写
function fact(n){
if(n==1){
return 1;
}
return n*arguments.callee(n-1);
}
- caller
// 可以追踪的从哪里被调用 ,全局调用, 则是 null
function test1(){
console.log(test1.caller);
}
function test2(){
console.log(test2.caller);
test1();
}
test2(); // null test(){....}
- prototype
prototype 默认有个 constructor 属性, 指向函数本身
typeof function === ‘object’, 所以函数也是个对象.
只要是对象(除了null), 就可以往上叠属性
直接在function
上追加方法 , 比较直观, 就类似访问属性一样方法方法
在 prototype 上定义的方法有点特殊
// 1. 函数上挂载方法
function test(){
}
test.sayHello = function(){
console.log('hello')
}
test.sayHello(); // 'hello'
// 2. function 的 prototype 属性上挂载方法
test.prototype.sayHi = function(){
console.log('hi');
}
test.prototype.sayHi();
// 3. new 实例
var test1 = new test();
test1.sayHi(); // 'hi'
test1.sayHello(); // 找不到 sayHello 方法
test1.__proto__.sayHi === test.prototype.sayHi ; // true
所以,
function
上直接定义的方法不能给new
出的实例共享 , 而定义在prototype
上的方法可以. 而实例是通过__proto__
链条, 也就是原型链找到的方法
- __proto__ , 原型链链条, 提现在
new
创建实例的时候
function test(){
}
test.prototype.a = 1
var test1 = new test();
test1.a === 1 ; // true
test1.__proto__ === test.prototype; // true
// test1 上并不存在 a 属性, 于是从原型链上查找
// __proto__ 的指向也是判断是否子类, instanceof 的核心
function test2(){}
test2.prototype.a = 2;
// 改变 test1 的 __proto__ 指向
test1.__proto__ = test2.prototype
test1 instanceof test ; // false
test1 instanceof test2; // true
console.log(test1.a) ; // 2
- [[Scopes]] => Array 类型
js 代码不可以直接访问属性.
js 是静态作用域, 也就是在预编译的时候, 函数的作用域就已经确定 , 并且保存在 functinon
上的 [[Scopes]]
. 而这也是函数在执行时, 查找变量的一部分区域.
函数朝赵变量有三个地方:
1. 局部变量
2. 参数
3. [[Scopes]]
[[Scopes]]
的生成类似栈结构, 根据函数所处位置, 所嵌套层数不同.
栈底是 global
也就是全局作用域
然后是外层函数的局部作用域, 依此类推.
而表现为数组, 那就是栈顶元素 ,为 [[Scopes]]
的第一个元素.
变量的查找, 先局部变量, 再参数表 , 再 [[Scopes]]
, [[Scopes]]
查找 ,也类似栈查找规则, 自顶而下.
function test(){
debugger
var a = 10 ;
function inner(){
console.log(a)
}
}
test();
// 断点调试结果如下
Closure
就在这了:
函数在更大的作用域范围里面 , 那么大的作用域就作为函数的闭包存在.
而对于全局作用域来说, 是全局内部定义的所有函数的闭包.
特殊函数 , 箭头函数#
- 执行时没有 arguments, 没有 caller , 尝试访问将报错:
Uncaught TypeError: 'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them
- 默认没有 prototype , 不能被 new, 手动添加 prototype 也不能 new
- this 的指向
var fn = (a,b)=>{
// Uncaught ReferenceError: arguments is not defined
console.log(arguments);
console.log(fn.arguments); // TypeError
console.log(fn.caller); // TypeError
}
// this 指向
// 1.
var test2 = () => {
console.log(this)
}
test(); // window
test2(); // window
// 2.
function Person(){}
Person.prototype.sayHi = function(){
console.log(this);
setTimeout(function(){ console.log(this) },1000);
setTimeout(()=>{ console.log(this) },2000);
}
var p1 = new Person();
p1.sayHi(); // p1 , window , p1
// 也就是说, 箭头函数执行时, 生成执行上下文时, this 不由拥有者决定, 而是继承外层来的, 不改变 this 的指向.
debugger 模式下 , local 中的 this 是 undefined , 实际却能打印出 window , 奇葩.
打印却是很诚实;
new 操作符#
new 操作符可以通过 function
生成一个对象, 也叫实例.
- 函数执行了一遍, 返回一个对象; 执行过程中, this 的指向为要生成的实例.
- 该对象的
__proto__
绑定到函数的prototype
- 实例可以通过原型链找到 function 定义在 prototype 上的方法
// 简单模拟
function New(constructorFn,...args){
// var obj = {};
// obj.__proto__ = constructorFn.prototype;
// 或者
var obj = Object.create(constructorFn.prototype);
constructorFn.apply(obj,args);
return obj;
}
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.sayHi = function () {
console.log(this.name)
}
var p1 = New(Person,'xx',18)
p1.sayHi(); // 'xx'
Object.create 就实现了:
1. 创建一个空对象
2. 该空对象的 proto 指向参数
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!