JavaScript 精粹 基础 进阶(6)函数和作用域(函数、this)

发布时间:2020-06-24 01:29:20 作者:huanghanzhilian
来源:网络 阅读:147

转载请注明出处

原文连接 http://blog.huanghanlian.com/article/5b698ef6b8ea642ea9213f4e

函数是一块JavaScript代码,被定义一次,但可执行调用多次,js中的函数也是对象,所以js函数可以像其他对象那样操作和传递所以我们也常叫js中的函数为函数对象。

函数概述

函数的构成主要有几个部分函数名,参数列表,函数体

JavaScript 精粹 基础 进阶(6)函数和作用域(函数、this)

function foo(x, y) {
    if (typeof x === 'number' && typeof y === 'number') {
        return x + y;
    } else {
        return 0;
    }
}
var cdr = foo(1,2);
console.log(cdr);//3

需要注意的是函数的返回值是依赖与return语句的,如果没有return语句,默认会在所有代码执行以后返回一个undefined。那么这是一般的函数调用。

function foo(x, y) {
    if (typeof x === 'number' && typeof y === 'number') {} else {

    }
}
var cdr = foo(1, 2);
console.log(cdr);//undefined

如果是作为构造器,外部使用new去调用的话,如果没有return语句,或者return是基本类型的话,那么会将this作为返回

function foo(x, y) {
    if (typeof x === 'number' && typeof y === 'number') {} else {
        return x+y;
    }
    this.x=x+y;
}
var cdr = new foo(1, 2);
console.log(cdr.x);//3

重点

this

函数在不同的调用方式下他的this指向是不一样的,并且不同的调用方式下也会有一些细微的差别。

arguments

函数里面有一个特殊的对象,叫arguments他和参数是有一定的连带关系的。

function foo(a,b) {
    return arguments;
}
var cdr=foo(1,2)
console.log(cdr);

JavaScript 精粹 基础 进阶(6)函数和作用域(函数、this)

不同的调用方式

直接调用 对象方法 构造器 call/apply/bind
foo() o.method() new Foo() func.call(o);
函数声明与表达式

创建函数有不同的方式,常见的两种就是函数声明函数表达式

函数声明

function foo(a, b) {
    a=+a;
    b=+b;
    if (isNaN(a)||isNaN(b)) {   //isNaN() 函数用于检查其参数是否是非数字值。是数字返回true,非数字返回false
        return;
    }
    return a+b;
}
var cdr = foo(1, 2);
console.log(cdr);//3

函数表达式

把一个匿名函数赋值给一个变量,这种就是函数表达式

var add = function(a, b) {
    //do sth
}

需要注意的是函数声明的函数可以在函数前调用

add();
function add() {
    console.log(1);//1
}

但是函数表达式会报错Uncaught TypeError: add is not a function

add();
var add = function() {
    alert(1)
}

函数表达式-立即执行函数表达式

把一个匿名函数用一个括号()括起来,然后再去直接调用,这种函数定义的方式呢,也叫作函数表达式,并且是立即执行函数表达式。

(function() {
    console.log(1); //1
})();

return 函数

我们也可以将函数对象,作为一个返回值,因为函数也是对象。这种形式也是函数表达式。

return function(){
    //do sth;
};

命名式函数表达式

这种并不常见,同样赋值给给一个函数,但是这个函数不是匿名函数,而是有一个名字的函数。这就是命名式函数表达式

var add = function add() {
    //do sth
};

除了函数声明和函数表达式创建函数以外,还有一种不常见的一种创建函数对象的方式。就是函数构造器Function构造器

Function构造器

函数构造器就是大写的Function

var add = new Function ('a','b','console.log(a+b);');
add(1,2);//3

var add = Function ('a','b','console.log(a+b);');
add(1,2);//3

可以通过new或者直接调用的方式 运行,他们俩基本没什么区别,

前面两个参数是函数对象里面的行参,最后一个参数表示函数体里面的代码。

用参数是字符串会带来安全上的一些隐患。

Function构造器去创建的函数里面,创建的变量仍然是局部变量,也可以使用立即调用。

Function ('var lop="local";console.log(lop);')();//Function构造器去创建的函数并且立即调用
console.log(lop);//lop是局部变量外部无法访问会报错

在立即调用函数中有lo变量,他的内部有一个df函数声明函数这个函数内可以拿到lo变量

(function() {
    var lo = "lo";

    function df() {
        console.log(lo)
    }
    df();
})();
//输出lo

但是在Function构造器中lo却访问不到。
在立即调用函数里,lo不可访问,全局变量l可以访问。所以说Function构造器很少使用

var l="l";
(function(){
    var lo="lo";
    Function ('console.log(l);console.log(lo);')();
})()

//l
//Uncaught ReferenceError: lo is not defined(…)

比一比三种创建函数的方式

函数声明 函数表达式 函数构造器
前置
允许匿名
立即调用
在定义该函数的作用域通过函数名访问
没有函数名
[JavaScript]this

在不同的环境下,同一个函数在不同的调用方式下,这个 this都有可能是不同的。

全局作用域下的this

全局作用域的 this一般指向全局对象,那么在浏览器里面这个全局对象就是window

console.log(this.document === document); //true
console.log(this === window); //true

可以看到this.document等价于window.document
this等价于window

this.a = 37;
console.log(window.a); //37

给全局对象添加一个属性a,这样就相当于创建了一个全局变量a,并且赋值为37。

一般函数的this(浏览器)

function f1() {
    return this;
}
console.log(f1() === window); //true

var f1 = function() {
    return this;
}
console.log(f1() === window); //true

用函数声明或者函数表达式,返回this,这里的this指向全局对象。

var f2 = function() {
    "use strict";
    return this;
}
console.log(f2()); //undefined

需要注意的是再严格模式下那么默认一般情况下this会指向

作为对象方法的函数的this

var o = {
    prop: 37,
    f: function() {
        return this.prop;
    }
};
console.log(o.f()); //37

创建了一个对象字面量oo里面有一个属性f值呢是一个函数对象,对于一个把函数作为对象属性值的方式,叫做对象的方法,作为对象的方法去调用o.f()这种情况下this会指向对象o所以o.f()被调用返回37。

var o = {
    prop: 37
};

function indep() {
    return this.prop;
}
o.f = indep;
console.log(o.f());//37

定义对象o只有一个属性prop赋值为37
这里买你有一个独立的indep函数,函数体return一个this.prop;如果直接去调用indep()那么这个this会指向window

如果用赋值方式o.fo对象创建属性f值为indep函数对象,那么这样去调用我们任然能够拿到37,console.log(o.f());//37

对象原型链上的this

Object.create({x:1});是系统内置的函数,这个函数会接收一个参数,一般是一个对象。他会返回一个新创建的对象,并且让这个对象的原型指向参数,参数一般是个对象。

var o = {
    f: function() {
        return this.a + this.b;
    }
};
var p = Object.create(o);
p.a = 1;
p.b = 4;
console.log(p);

JavaScript 精粹 基础 进阶(6)函数和作用域(函数、this)

原型链上有f方法

var o = {
    f: function() {
        return this.a + this.b;
    }
};
var p = Object.create(o);
p.a = 1;
p.b = 4;
console.log(p.f());//5

创建o对象属性f值是一个函数对象,

通过var p = Object.create(o);创建了一个对象p,这个对象p是一个空的对象,并且他的原型会指向o,使用p.a给对象p添加属性值为1p.b值为4,这样就创建在对象上的属性,那么我去调用p原型上的方法的时候,this.a + this.b;任然能取到对象上的a和b。

get/set方法与this

function modulus() {
    return Math.sqrt(this.re * this.re + this.im * this.im);
};

var o = {
    re: 1,
    im: -1,
    get phase() {
        return Math.atan2(this.im, this.re);
    }
};

Object.defineProperty(o, 'modulus', {
    get: modulus,
    enumerable: true,
    configurable: true
});

console.log(o.phase, o.modulus);//-0.7853981633974483 1.4142135623730951

构造器中的this

function MyClass() {
    this.a = 23;
};
var o = new MyClass();
console.log(o.a); //23

如果正常去调用一个函数的话MyClass()那么这个函数的this会指向window但是如果我用new实例化构造函数去调用,那么这里面的this会指向这样一个空的对象,并且这个对象的原型会指向MyClass.prototype
最后这个this.a会作为返回值,因为这里没有return所以默认this会作为返回值,所以这个对象o就会就会有a属性值23。

function c2() {
    this.a = 23;
    return {
        a: 24
    };
}
var f = new c2();
console.log(f.a); //24

c2函数this等于23,但是这一次return返回了一个对象,那么这个a就不再是23了,而是返回的这个对象,o.a属性就变成了24。

如果是作为构造器,外部使用new去调用的话,如果没有return语句,或者return是基本类型的话,那么会将this作为返回

call/apply方法与this

function add(c, d) {
    return this.a + this.b + c + d;
};

var o = {
    a: 1,
    b: 3
};

var h=add.call(o, 5, 7);//1+3+5+7
console.log(h)

add.apply(o, [10, 20]);//1+3+10+20

function bar() {
    console.log(Object.prototype.toString.call(this));
};

bar.call(7);//[object Number]

函数add,这里面返回this.a + this.b + c + d;把这四个数相加起来,定义o对象有两个属性a和b,通过对象call方法,.call(o, 5, 7)第一个参数是你想作为this的这样一个对象,5和7是想要添加的参数,5赋值给function add(c, d)的c,7赋值给d;最终的结果就是1+3+5+7
add.call(o, 5, 7)add.apply(o, [10, 20])基本没什么差别,只是apply是数组作为传参的。

bind方法与this

function f() {
    return this.a;
};

var g = f.bind({
    a: "test"
});
console.log(g()); //test

var o = {
    a: 23,
    f: f,
    g: g
};
console.log(o.f(), o.g()); //23 "test"

在EcmaScript5中扩展了叫bind的方法(IE6,7,8不支持)

这里有个函数对象f,通过bind方法,发现f.bind({ a: "test"});有一个参数,这个参数是一个对象,这个对象就是你想要将某一个对象作为this的时候那就把这样一个对象穿进去,那么我们拿到一个新的g对象,新的g对象在调用的时候this已经指向了bind的参数,这对于我们需要去绑定一次重复调用仍然实现绑定,这样会比call,apply会更加高效点。

还可以看到,这里有一个o然后把a赋值为23,f属性赋值为f函数,g赋值绑定之后的方法,输出f的时候能拿到23,一般的函数根据调用方式来判断,他是通过对象属性去调用的,那么这里买你的this就会指向o那么就会拿到a:23。这里面比较特殊的我使用bind的方法去绑定了之后,即使我们把 新绑定之后的方法作为对象的属性去调用,仍然会按照之前的绑定去走。所以返回"test"

[JavaScript]函数属性arguments

函数属性 & arguments

接触函数属性和方法

function foo(a, b, d) {
    console.log(arguments);
    console.log(arguments.length); //2
    console.log(arguments[0]); //1
    arguments[1] = 10;
    console.log(b); //change to 10;

    arguments[2] = 100;
    console.log(d); //即使手动赋值任然是undefined. 没有赋值arguments[2]=100;d等于undefined
    console.log(arguments.callee === foo);
}
foo(1, 2);
console.log(foo.length);
console.log(foo.name);
console.dir(foo);

JavaScript 精粹 基础 进阶(6)函数和作用域(函数、this)

定义函数foo
通过foo.name获取函数名
通过foo.length获取函数行参个数
通过arguments.length获取函数实际传参的个数

有一个问题。foo(1, 2);传参只有两个参数,实际上d是没有传进来的, 这种情况尝试去arguments[2] = 100;去赋值的时候对应的d仍然是undefined

严格模式下

function foo(a, b, d) {
    'use strict'
    console.log(arguments.length); //2
    console.log(arguments[0]); //1
    arguments[1] = 10;
    console.log(b); //严格模式下仍然是1

    arguments[2] = 100;
    console.log(d); //即使手动赋值任然是undefined. 没有赋值arguments[2]=100;d等于undefined
    console.log(arguments.callee === foo);  //严格模式下arguments.callee是禁止使用
}
foo(1, 2);
console.log(foo.length);
console.log(foo.name);

apply/call方法

function foo(x, y) {
    console.log(x, y, this);
};
foo.call(100, 1, 2);                    //1 2 Number {[[PrimitiveValue]]: 100}
foo.apply(true, [3, 4]);                //3 4 Boolean {[[PrimitiveValue]]: true}
foo.apply(null);                        //undefined undefined Window {...}
foo.apply(undefined);                   //undefined undefined Window {...}

定义foo函数,有x,y两个属性,输出参数和对应的this

对于call()来讲第一个参数就是想作为this的对象如果不是对象他会转成对象,所以这里的foo.call(100, 1, 2);100会转成对应的包装类Number值为100。类似的foo.apply(true, [3, 4]);true会转换成布尔值。
如果第一个参数是null,undefined的话,那么this会指向全局对象,对于浏览器就是window对象

需要注意在严格模式下

function foo(x, y) {
    'use strict';
    console.log(x, y, this);
};
foo.call(100, 1, 2);                    //1 2 100
foo.apply(true, [3, 4]);                //3 4 true
foo.apply(null);                        //undefined undefined null
foo.apply(undefined);                   //undefined undefined undefined

在严格模式下this输出直接量

bind方法

在EcmaScript5中扩展了叫bind的方法(IE6,7,8不支持)

this.x = 9; //创建了一个全局变量x并赋值为9
var mod = {
    x: 81,
    getX: function() {
        return this.x;
    }
};

console.log(mod.getX()); //81

var gitX = mod.getX;
console.log(gitX());; //9

var bound = gitX.bind(mod);
console.log(bound());//81

创建了一个全局变量x并赋值为9,然后有一个变量mod赋值为一个对象字面量,里面有x属性 值为81,有一个getX属性,值是一个匿名函数,他会返回this.x

如果对象(点)属性名 mod.getX()这样的调用方式。会返回这个对象为this返回81。

var gitX = mod.getX;如果定义一个全局变量gitX赋值为mod对象的getX属性,这个时候this就会指向全局对象,也就是this.x = 9;所以返回9,console.log(gitX());; //9

通过bind方法改变函数运行时的this指向,var bound = gitX.bind(mod);也就是说gitX函数在运行时thismod对象。所以再次输出81。

bind与currying 科里化功能

函数科里化就是把一个函数拆成多个单元。

function add(a,b,c){
    return a+b+c;
};

var func=add.bind(undefined,100);
func(1,2);//103

var func2=func.bind(undefined,200);
func2(10);//310

这里有一个函数add他的作用是把参数abc相加然后作为返回值,

可能有的时候不需要一次把函数都调用完,而是我调用一次把前面两个参数传完了后,得到这样的函数我再去调用,传入第三个值,比如通过add.bind()方法这次不需要改变this,那写个undefined,提高额外参数100,这个我们拿到bind函数以后,相当于这个100就会固定赋值给a第一个参数,在调用func(1,2);传入1和2的时候,1就会给b,2就会给c,所以最后的答案就是103。

类似的再去func.bind()也就是说绑定了两个参数ab,b传入200,再去调用一次func2(10)传入10,那么就是100+200+10,最终结果就是310。

实际例子

JavaScript 精粹 基础 进阶(6)函数和作用域(函数、this)

bind与new

function foo() {
    this.b = 100;
    return this.a;
};

var func = foo.bind({
    a: 1
});
console.log(func()); //1

var func2 = new func();
console.log(func2); //{b: 100}

foo函数声明,函数体this.b=100return this.a;那如果我直接调用的话,this.b=100中的this是指向全局对象,所以说相当于创建了一个全部变量b并赋值为100,返回全局对象上的a属性。

使用bind()方法,var func = foo.bind({ a: 1 });来传入一个参数,一个字面量对象只有一个属性a,直接调用的话func(),那么foo函数中的this就会指向bind({a:1})的参数。所以调用func()返回1。

如果使用new的话就特殊些,使用new除非func()函数return 除非是对象如果不是对象将会把this作为返回值,并且this会被初始化成一个默认的空对象,这个空对象的原型是foo.prototype,所以使用new去调用的时候var func2 = new func();,即使用了var func = foo.bind({ a: 1 });bind方法但是这个this任然会指向没有bind的时候的一样,所以返回值会忽略return,返回this对象。

推荐阅读:
  1. JavaScript基础
  2. JavaScript的学习路线

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

js 深入 浅出

上一篇:mac 下装django

下一篇:7.2bash 脚本选项及组合条件测试

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》