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 指向参数

总结

猜你喜欢

转载自blog.csdn.net/haokur/article/details/80489393