js闭包函数

更新日期: 2019-06-03阅读: 1.9k标签: 函数

闭包函数

什么是闭包函数?

闭包函数是一种函数的使用方式,最常见的如下:

function fn1(){
    function fn(){

    }
    return fn;
}

这种函数的嵌套方式就是闭包函数,这种模式的好处是可以让内层函数访问到外层函数的变量,并且让函数整体不至于因为函数的执行完毕而被销毁。

例如:

function fn1(){
    var a =10;
    function fn(){
        console.log(a); // 10
    }
    return fn;
}

再比如下面的代码,随着函数的每次执行,变量的值都会进行递增1,原因是因为外层函数的变量处于内层函数的作用域链当中,被内层函数所使用着,当js垃圾回收机制读取到这一情况后就不会进行垃圾回收。

例如:

function fn1(){
    var a = 1;
    function fn(){
        a++;
        console.log(a);
    }
    return fn;
}

// 调用函数
var x  = fn1();
x(); // 2
x();//3

闭包函数在js的开发当中是非常常见的写法,例如下面这种写法,功能是实现了对数组的一些常规操作的封装,也是属于对闭包函数的一种应用。

let Utils = (function(){
    var list = [];
    return {
        add:function(item){
            if(list.indexOf(item)>-1) return; // 如果数组内元素存在,那么不在重复添加
            list.push(item);
        },
        remove:function(item){
            if(list.indexOf(item) < 0) return; // 如果要删除的数组数组之内不存在,那么就返回
            list.splice(list.indexOf(item),1);
        },
        get_length:function(){
            return list.length;
        },
        get_showData:function() {
            return list;
        }
    }
})();
Utils.add("hello,world");
Utils.add("this is test");
console.log(Utils.get_showData());// ["hello,world","this is test"]

在上面的代码中,函数嵌套函数形成了闭包函数的结构,在开发中是比较常见的写法。

闭包的概念:

闭包是指有权限访问上一级父作用域的变量的函数.


立即执行函数(IIFE)

在js开发中,经常碰到立即执行函数的写法。大体如下:

// 下面的这种写法就是立即执行函数
// 在函数内部的内容会自动执行
(function(){
    var a = 10;
    var b = 20;
    console.log(a+b); // 30
})();

我们也可以通过第二个括号内传入参数:

(function(i){
    console.log(i);
})(i);

这种自调用的写法本质上来讲也是一个闭包函数。

通过这种闭包函数,我们可以有效的避免变量污染等问题,从而创建一个独立的作用域。

但是问题相对来说也很明显,就是在这个独立的作用域当中,我们没有办法将其中的函数或者变量让外部访问的到。所以如果我们在外部需要
访问这个立即执行函数中的变量或者方法,我们就需要通过第二个括号将window这个全局的变量对象传入,并且将需要外部访问的变量或者函数赋值
给window,这样做相当于将其暴露在了全局的作用域范围之内。

需要注意的是,通常情况下我们只需要将必要的方法暴露,这样才能保证代码并不会相互产生过多的影响,从而降低耦合度。

例如:

(function (window){
    var a = 10; // 私有属性

    function show(){
        return a++;
    }


    function sayHello(){ // 私有方法
        alert("hello,world");
    }
    window.show = show;// 将show方法暴露在外部
})(window);

需要理解的是,在很多的代码中,总是在(function(){})()的最前面加上一个;,目的是为了防止合并代码的时候js将代码解析成(function(){})()(function(){})()这种情况。 

 

闭包函数的变异

因为js的特殊性,所以很多时候我们在学习js的时候,除了js代码的语法以外,还要学习很多为了解决实际问题的方案,例如下面的这种写法就是为了
实现module的写法。

例如:

var testModule = function(){
    var name = "张三"; // 私有属性,外部无法访问

    return {
        get_name:function(){ // 暴露在外部的方法
            alert(name);
        },
        set_name:function(new_name){ // 暴露在外部的方法
            name = new_name;
        }
    }
}

我们也可以将这种写法进行升级,和立即执行函数进行适度的结合也是常见的写法:

例如:

var blogModule = (function (my) {
    my.name = "zhangsan";
    // 添加一些功能   
    my.sayHello = function(){
        console.log(this.name)
    }
    return my;
} (blogModule || {}));  

console.log(blogModule.sayHello())


自调用函数(自执行匿名函数(Self-executing anonymous function))和立即执行函数的区别

自调用函数其实也就是递归函数  

自调用函数顾名思义,就是调用自身的函数,而立即执行函数则是立即会及执行的函数。

下面是二者的一些比较:

// 这是一个自执行的函数,函数内部执行自身,递归
function foo() { foo(); }

// 这是一个自执行的匿名函数,因为没有标示名称
// 必须使用arguments.callee属性来执行自己
var foo = function () { arguments.callee(); };

// 这可能也是一个自执行的匿名函数,仅仅是foo标示名称引用它自身
// 如果你将foo改变成其它的,你将得到一个used-to-self-execute匿名函数
var foo = function () { foo(); };

// 有些人叫这个是自执行的匿名函数(即便它不是),因为它没有调用自身,它只是立即执行而已。
(function () { /* code */ } ());

// 为函数表达式添加一个标示名称,可以方便Debug
// 但一定命名了,这个函数就不再是匿名的了
(function foo() { /* code */ } ());

// 立即调用的函数表达式(IIFE)也可以自执行,不过可能不常用罢了
(function () { arguments.callee(); } ());
(function foo() { foo(); } ());


作用域与作用域链

作用域

所谓的作用域,指的就是变量和函数的可访问范围,控制着变量和函数的可见性与生命周期,
在JavaScript中变量的作用域有全局作用域和函数作用域以及ES6新增加的块级作用域。

例如,在函数外部通过var关键字声明的变量就是全局变量,作用域的范围也就是全局作用域,而在函数内部
通过var声明或者let声明的就是局部变量,作用域仅限于函数内部,在{}内部或者流程控制语句或者循环语句内部
通过let声明的变量作用域范围则仅限于当前作用域。函数的参数在()内部,只能在函数内部使用,作用域范围也仅限于函数。
同时window对象的所有属性也拥有全局作用域。

例如:


// 作用域范围
var a = 10; // 全局

function fn1(a,b){ // 函数fn1内

    c = 30; // 全局
    var x = 30; // 函数fn1内  

    function fn2(){
        var s = "hello"; // 函数fn2内
        console.log(x); // 30 函数内部可以访问外层的变量
    }
}

for(var i =0;i<10;i++){} // 循环体内声明的计数变量i也是一个全局

console.log(i); // 10

for(let j = 0;i<10;j++){} // let 声明的计数变量j 是一个局部  

console.log(j);// 出错,访问不到

执行环境上下文

上面我们说到了作用域,下面再来说下执行环境(execution context)。

什么是执行环境呢?

简单点说,执行环境定义了变量或者函数有权访问的其他的数据,并且也决定了他们各自的行为。需要知道的
是,每一个执行环境当中,都有着一个与之关联的变量对象(variable object),执行环境中定义的所有变量和函数都会保存在这个对象中,解析器在处理数据的时候就会访问这个内部对象。

而全局执行环境是最外层的一个执行环境,在web浏览器中最外层的执行环境关联的对象是window,所以我们
可以这样说,所有全局变量和函数 都是作为window对象的属性和方法创建的。

我们创建的每一个函数都有自己的执行环境,当执行流进行到函数的时候,函数的环境会被推入到一个函数执行栈
当中,而在函数执行完毕后执行环境出栈并被销毁,保存在其中的所有变量和函数定义随之销毁,控制权返回到之前的执行环境中,全局的执行环境在应用程序退出(浏览器关闭)才会被销毁。

作用域链

当代码在一个执行环境执行之时,会创建一个变量对象的一个作用域链(scope chain),来保证在执行环境中
,对执行环境有权访问的变量和函数的有序访问。

作用域第一个也d就是顶层对象始终是当前执行代码所在环境的变量对象(VO)。

例如:

function fn1(){}

fn1在创建的时候作用域链被添加进全局对象,全局对象中拥有所有的全局变量。

例如上面的fn1在创建的时候,所处的环境是全局环境,所以此时的this就指向window。

在函数运行过程中标识符的解析是沿着作用域链一级一级搜索的过程,从第一个对象开始,逐级向后回溯,直到找到同名标识符为止,找到后不再继续遍历,找不到就报错。

如果执行环境是函数,那么将其活动对象(activation object, AO)作为作用域链第一个对象,第二个对象是包含环境,下一个是包含环境上一层的包含环境...

也就是说所谓的作用域链,就是指具体的某个变量或者函数从其第一个对象(活动对象)一直到顶层执行环境。这中间的联系就是作用域链。


被人误解的闭包函数

谈及闭包函数的概念,经常会有人错误的将其理解为从父上下文中返回内部函数,甚至理解成只有匿名函数才能是闭包。

而实际来说,因为作用域链,使得所有的函数都是闭包(与函数类型无关: 匿名函数,FE,NFE,FD都是闭包)。

注意:这里只有一类函数除外,那就是通过Function构造器创建的函数,因为其[[Scope]]只包含全局对象。  


闭包函数的应用

闭包函数是js当中非常重要的概念,在诸多的地方可以应用到闭包,通过闭包,我们可以写出很多优秀的代码,下面是一些常见的内容:

例如:

// 数组排序
[1, 2, 3].sort(function (a, b) {
  ... // 排序条件
});

// map方法的应用,根据函数中定义的条件将原数组映射到一个新的数组中
[1, 2, 3].map(function (element) {
  return element * 2;
}); // [2, 4, 6]

// 常用的 forEach
[1, 2, 3].forEach(function (element) {
  if (element % 2 != 0) {
    alert(element);
  }
}); // 1, 3

例如我们常用的call和apply方法,它们是两个应用函数,也就是应用到参数中的函数(在apply中是参数列表,在call中是独立的参数):

例如:

(function () {
  alert([].join.call(arguments, ';')); // 1;2;3
}).apply(this, [1, 2, 3]);

还有最常使用的写法:

var a = 10;
setTimeout(function () {
  alert(a); // 10, after one second
}, 1000);

当然,ajax的写法也就是回调函数其实本质也是闭包:

//...
var x = 10;
// only for example
xmlHttpRequestObject.onreadystatechange = function () {
  // 当数据就绪的时候,才会调用;
  // 这里,不论是在哪个上下文中创建
  // 此时变量“x”的值已经存在了
  alert(x); // 10
};
//...

当然也包括我们上边说的封装独立作用域的写法:

例如:

var foo = {};

// 初始化
(function (object) {

  var x = 10;

  object.getX = function _getX() {
    return x;
  };

})(foo);

alert(foo.getX()); // 获得闭包 "x" – 10


JSON对象和JS对象直接量

在工作当中,我们总是可以听到人说将数据转换为JSON对象,或者说把JSON对象转换为字符串之类的话,下面是关于JSON的具体说明。

JSON对象并不是JavaScript对象字面量(Object Literals)

很多人错误的将JSON认为是JavaScript当中的对象字面量(object Literals),原因非常简单,就是因为它们的语法是非常相似的,但是在ECMA中明确的说明了。JSON只是一种数据交互语言,只有我们将之用在string上下文的时候它才叫JSON。

序列化与反序列化

2个程序(或服务器、语言等)需要交互通信的时候,他们倾向于使用string字符串因为string在很多语言里解析的方式都差不多。复杂的数据结构经常需要用到,并且通过各种各样的中括号{},小括号(),叫括号<>和空格来组成,这个字符串仅仅是按照要求规范好的字符。

为此,我们为了描述这些复杂的数据结构作为一个string字符串,制定了标准的规则和语法。JSON只是其中一种语法,它可以在string上下文里描述对象,数组,字符串,数字,布尔型和null,然后通过程序间传输,并且反序列化成所需要的格式。

常见的数据流行交互格式有YAML、XML、和JSON都是常用的数据交互格式。

字面量
引用Mozilla Developer Center里的几句话,供大家参考:

  1. 他们是固定的值,不是变量,让你从“字面上”理解脚本。 (Literals)
  2. 字符串字面量是由双引号(")或单引号(')包围起来的零个或多个字符组成的。(Strings Literals)
  3. 对象字面量是由大括号({})括起来的零个或多个对象的属性名-值对。(Object Literals)

什么时候会成为JSON

JSON是设计成描述数据交换格式的,他也有自己的语法,这个语法是JavaScript的一个子集。
{ "prop": "val" } 这样的声明有可能是JavaScript对象字面量也有可能是JSON字符串,取决于什么上下文使用它,如果是用在string上下文(用单引号或双引号引住,或者从text文件读取)的话,那它就是JSON字符串,如果是用在对象字面量上下文中,那它就是对象字面量。

例如:

// 这是JSON字符串
var foo = '{ "prop": "val" }';

// 这是对象字面量
var bar = { "prop": "val" };

而且要注意,JSON有非常严格的语法,在string上下文里{ "prop": "val" } 是个合法的JSON,但{ prop: "val" }和{ 'prop': 'val' }确实不合法的。所有属性名称和它的值都必须用双引号引住,不能使用单引号。

JS当中的JSON对象

目前,JSON对象已经成为了JS当中的一个内置对象,有两个静态的方法:JSON.parse和JSON.stringify。

JSON.parse主要要来将JSON字符串反序列化成对象,JSON.stringify用来将对象序列化成JSON字符串。老版本的浏览器不支持这个对象,但你可以通过json2.js来实现同样的功能。

例如:

// 这是JSON字符串,比如从AJAX获取字符串信息
var my_json_string = '{ "prop": "val" }';

// 将字符串反序列化成对象
var my_obj = JSON.parse( my_json_string );

alert( my_obj.prop == 'val' ); //  提示 true, 和想象的一样!

// 将对象序列化成JSON字符串
var my_other_json_string = JSON.stringify( my_obj );


对象的创建

javascript中对象的创建有很多种方式,如下:

  1. 第一种方式是通过构造函数的形式创建成一个对象
  2. 第二种形式是通过{}这种写法直接来创建一个对象,还有一种是通过new Object()的形式创建构造函数。

通过对象字面量的形式创建对象

例如:

let obj = {
    value:10,
    setValue:function(new_value){
        this.value = new_value;
    },
    geyValue:function(){
        return this.value;
    }
}

通过构造函数的形式创建一个对象

例如:

function Person(name,age){
    this.name = name;
    this.age = age;
}

var a = new Person("张三",30);

通过new Object()创建对象

例如:

var obj = new Object();

console.log(obj); // {}


对象直接量创建的对象的使用方式

下面来说一下通过对象直接量创建的对象的一些使用方式:

属性和方法的增删改查:
例如:

// 创建一个对象直接量
var person = {
    name:"张三",
    age:40,
    sayHello:function(){
        alert(this.name,"你好");
    },
    setName:function(name){
        this.name = name;
    }
}

// 更改属性
person.name = "李四";
person['age'] = 40;

// 更改方法
person.sayHello = function(){
    return this.name + "你好";
}


// 添加属性
person.like = "旅游";

// 添加方法
person.run = function(){
    return `${this.name} 正在跑步`;
}

// 删除
delete person.age;

// 查询和调用
console.log(person.name);

console.log(person.run());


构造函数创建的对象常见的操作方式

例如:

function Person(name,age){
    this.name = name; // 初始化属性
    this.age = age;
}

Person.prototype.sayHello  = function(){
    alert(this.name);
}

// 实例化
var zhangsan = new Person("张三",20);

// 查看属性
console.log(zhangsan.name);

//. 调用方法
zhangsan.sayHello();

// 修改属性
zhangsan.name = "张小三";

// 修改方法
zhangsan.sayHello = function(){
    console.log(this.name + "你好");
}


// 添加属性和调用属性相同 ...
// 添加方法和调用方法相同 ...

// 删除
delete zhangsan.name;
console.log(zhangsan);


对象的getter 和 setter

在js对象当中,存在两个属性getter和setter,通过这两个属性我们可以进行属性的查询和赋值。

例如:

var obj =  {
    value:10,

    get add(){
        return this.value + 1;
    },
    set changeVal(new_val){
        return this.value = new_val;
    }
}

// 使用
console.log(obj.add); // 11
obj.changeVal = 20;
console.log(obj.value); // 20

应用:变量监听

例如:
html代码如下

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>变量的动态监听</title>
</head>
<body>
    <button id="btn" onclick="test()">点击</button>
    <p id="p">0</p>
</body>
<script src="test.js" charset="utf-8"></script>
<script type="text/javascript">
    function test(){
        watchVal.value = ++watchVal.value;
        let info = document.getElementById("p");
        info.innerHTML = watchVal.value;
        console.log(watchVal.value);
    }
</script>
</html>

js代码如下:

// 变量监听
var watchVal = {
    value:0,
    get val(){
        console.log('取值:',this.value);
        return this.value;
    },
    set val(vals) {
        this.value = vals;
        console.log("存储之后的值",this.value);
    }
}


原型和原型链

在诸多面向对象编程语言当中,封装、继承、多态是必备的内容,而在js这门基于对象的编程语言身上,想要实现其诸多特性,只能
通过prototype原型来实现。

我们可以先来通过代码来简单的体验一下原型:

// 创建一个数组
var arr = [1,2,3,4];

// 打印这个数组
console.log(arr);

你可以在打印的结果中最后看到一个属性__proto__。

你过你尝试着将其打印,你会发现其中包含了Array的属性和方法。

它是什么呢?

它其实就是数据arr数组的原型,而__proto__表述的其实就是数组的原型链.

你需要知道的是,在js当中,__proto__就是数据原型的代表,我们可以通过其向我们创建的数组中添加方法:

例如:

// 创建一个数组
var arr = [1,2,3,4];

// 打印这个数组
console.log(arr);

// 向数组的原型上添加一个sayHello()方法
arr.__proto__.sayHello = function(){
    console.log("hello,world!");
}

arr.sayHello();

我们在开发的过程中,更多的是采用下面的写法:

// 创建一个数组
var arr = [1,2,3,4];

// 打印这个数组
console.log(arr);

// 向数组的原型上添加一个sayHello()方法
Array.prototype.sayHello = function(){
    console.log("hello,world!");
}

arr.sayHello();

在上面的代码中,prototype同样表示着原型,只不过更多的时候是被应用在构造函数的身上。Array是数组对象的原型
,它是一个构造函数,所以可以通过这个属性来进行方法的设置。

当数据是一个构造函数的时候,prototype的使用方式还可以进行简化:


function Person(){

}

Person.prototype = {
    sayHello:function(){
        console.log(111)
    }
}


var a = new Person();
a.sayHello();


面向对象

在编程领域,面向对象是一种常见的编程范式,也被成为OOP,ECMASript支持包括结构化、面向对象、函数式、命令式等多种编程方式。

下面要说的就是面向对象编程的内容。

MDN说明:

面向对象编程是用抽象方式创建基于现实世界模型的一种编程模式。它使用先前建立的范例,包括模块化,多态和封装几种技术。今天,许多流行的编程语言(如Java,JavaScript,C#,C+ +,Python,php,Ruby和Objective-C)都支持面向对象编程(OOP)。

相对于「一个程序只是一些函数的集合,或简单的计算机指令列表。」的传统软件设计观念而言,面向对象编程可以看作是使用一系列对象相互协作的软件设计。 在 OOP 中,每个对象能够接收消息,处理数据和发送消息给其他对象。每个对象都可以被看作是一个拥有清晰角色或责任的独立小机器。

面向对象程序设计的目的是在编程中促进更好的灵活性和可维护性,在大型软件工程中广为流行。凭借其对模块化的重视,面向对象的代码开发更简单,更容易理解,相比非模块化编程方法, 它能更直接地分析, 编码和理解复杂的情况和过程。

面向对象编程术语:

Namespace 命名空间
    允许开发人员在一个独特,应用相关的名字的名称下捆绑所有功能的容器。
Class 类
    定义对象的特征。它是对象的属性和方法的模板定义。
Object 对象
    类的一个实例。
Property 属性
    对象的特征,比如颜色。
Method 方法
    对象的能力,比如行走。
Constructor 构造函数
    对象初始化的瞬间,被调用的方法。通常它的名字与包含它的类一致。
Inheritance 继承
    一个类可以继承另一个类的特征。
Encapsulation 封装
    一种把数据和相关的方法绑定在一起使用的方法。
Abstraction 抽象
    结合复杂的继承,方法,属性的对象能够模拟现实的模型。
Polymorphism 多态
    多意为「许多」,态意为「形态」。不同类可以定义相同的方法或属性。

JS当中的命名空间

命名空间是一个容器,它允许开发人员在一个独特的,特定于应用程序的名称下捆绑所有的功能。 在JavaScript中,命名空间只是另一个包含方法,属性,对象的对象。

创造的JavaScript命名空间背后的想法很简单:一个全局对象被创建,所有的变量,方法和功能成为该对象的属性。使用命名空间也最大程度地减少应用程序的名称冲突的可能性。

例如:
我们来创建一个全局变量叫做 MYAPP

// 全局命名空间
var MYAPP = MYAPP || {};

在上面的代码示例中,我们首先检查MYAPP是否已经被定义(是否在同一文件中或在另一文件)。如果是的话,那么使用现有的MYAPP全局对象,否则,创建一个名为MYAPP的空对象用来封装方法,函数,变量和对象。

我们也可以创建子命名空间:

// 子命名空间
MYAPP.event = {};

下面是用于创建命名空间和添加变量,函数和方法的代码写法:

// 给普通方法和属性创建一个叫做MYAPP.commonMethod的容器
MYAPP.commonMethod = {
  regExForName: "", // 定义名字的正则验证
  regExForPhone: "", // 定义电话的正则验证
  validateName: function(name){
    // 对名字name做些操作,你可以通过使用“this.regExForname”
    // 访问regExForName变量
  },

  validatePhoneNo: function(phoneNo){
    // 对电话号码做操作
  }
}

// 对象和方法一起申明
MYAPP.event = {
    addListener: function(el, type, fn) {
    //  代码
    },
   removeListener: function(el, type, fn) {
    // 代码
   },
   getEvent: function(e) {
   // 代码
   }

   // 还可以添加其他的属性和方法
}

//使用addListener方法的写法:
MYAPP.event.addListener("yourel", "type", callback);

自定义对象

[类]
JavaScript是一种基于原型的语言,它没类的声明语句,比如C+ +或Java中用的。这有时会对习惯使用有类申明语句语言的程序员产生困扰。相反,JavaScript可用方法作类。定义一个类跟定义一个函数一样简单。在下面的例子中,我们定义了一个新类Person。

function Person() { }
// 或
var Person = function(){ }

[对象(类的实例)]

我们使用 new obj 创建对象 obj 的新实例, 将结果(obj 类型)赋值给一个变量方便稍后调用。

在下面的示例中,我们定义了一个名为Person的类,然后我们创建了两个Person的实例(person1 and person2).

function Person() { }
var person1 = new Person();
var person2 = new Person();

[构造器]

在实例化时构造器被调用 (也就是对象实例被创建时)。构造器是对象中的一个方法。 在JavaScript中函数就可以作为构造器使用,因此不需要特别地定义一个构造器方法,每个声明的函数都可以在实例化后被调用执行。

构造器常用于给对象的属性赋值或者为调用函数做准备。 在本文的后面描述了类中方法既可以在定义时添加,也可以在使用前添加。

在下面的示例中, Person类实例化时构造器调用一个 alert函数。

function Person() {
  alert('Person instantiated');
}

var person1 = new Person();
var person2 = new Person();

[属性(对象属性)]
属性就是 类中包含的变量;每一个对象实例有若干个属性. 为了正确的继承,属性应该被定义在类的原型属性 (函数)中。

可以使用 关键字 this调用类中的属性, this是对当前对象的引用。 从外部存取(读/写)其属性的语法是: InstanceName.Property; 这与C++,Java或者许多其他语言中的语法是一样的 (在类中语法 this.Property 常用于set和get属性值)

在下面的示例中,我们为定义Person类定义了一个属性 firstName 并在实例化时赋初值。

function Person(firstName) {
  this.firstName = firstName;
  alert('Person instantiated');
}

var person1 = new Person('Alice');
var person2 = new Person('Bob');

// Show the firstName properties of the objects
alert('person1 is ' + person1.firstName); // alerts "person1 is Alice"
alert('person2 is ' + person2.firstName); // alerts "person2 is Bob"

[方法(对象属性)]
方法与属性很相似, 不同的是:一个是函数,另一个可以被定义为函数。 调用方法很像存取一个属性, 不同的是add () 在方法名后面很可能带着参数. 为定义一个方法, 需要将一个函数赋值给类的 prototype 属性; 这个赋值给函数的名称就是用来给对象在外部调用它使用的。

在下面的示例中,我们给Person类定义了方法 sayHello(),并调用了它.

function Person(firstName) {
  this.firstName = firstName;
}

Person.prototype.sayHello = function() {
  alert("Hello, I'm " + this.firstName);
};

var person1 = new Person("Alice");
var person2 = new Person("Bob");

// call the Person sayHello method.
person1.sayHello(); // alerts "Hello, I'm Alice"
person2.sayHello(); // alerts "Hello, I'm Bob"

在JavaScript中方法通常是一个绑定到对象中的普通函数, 这意味着方法可以在其所在context之外被调用。

function Person(firstName) {
  this.firstName = firstName;
}

Person.prototype.sayHello = function() {
  alert("Hello, I'm " + this.firstName);
};

var person1 = new Person("Alice");
var person2 = new Person("Bob");
var helloFunction = person1.sayHello;

person1.sayHello();                                 // alerts "Hello, I'm Alice"
person2.sayHello();                                 // alerts "Hello, I'm Bob"
helloFunction();                                    // alerts "Hello, I'm undefined" (or fails
                                                    // with a TypeError in strict mode)
console.log(helloFunction === person1.sayHello);          // logs true
console.log(helloFunction === Person.prototype.sayHello); // logs true
helloFunction.call(person1);                        // logs "Hello, I'm Alice"

如上例所示, 所有指向sayHello函数的引用 ,包括 person1, Person.prototype, 和 helloFunction 等, 均引用了相同的函数.

在调用函数的过程中,this的值取决于我们怎么样调用函数. 在通常情况下,我们通过一个表达式person1.sayHello()来调用函数:即从一个对象的属性中得到所调用的函数。此时this被设置为我们取得函数的对象(即person1)。这就是为什么person1.sayHello() 使用了姓名“Alice”而person2.sayHello()使用了姓名“bob”的原因。

然而我们使用不同的调用方法时, this的值也就不同了。当从变量 helloFunction()中调用的时候, this就被设置成了全局对象 (在浏览器中即window)。由于该对象 (非常可能地) 没有firstName 属性, 我们得到的结果便是"Hello, I'm undefined". (这是松散模式下的结果, 在 严格模式中,结果将不同(此时会产生一个error)。 但是为了避免混淆,我们在这里不涉及细节) 。

call和apply可以显示的更改this  

继承

创建一个或多个类的专门版本类方式称为继承(Javascript只支持单继承)。 创建的专门版本的类通常叫做子类,另外的类通常叫做父类。 在Javascript中,继承通过赋予子类一个父类的实例并专门化子类来实现。在现代浏览器中你可以使用 Object.create 实现继承.

在下面的例子中, 我们定义了 Student类作为 Person类的子类. 之后我们重定义了sayHello() 方法并添加了 sayGoodBye() 方法.

// 定义Person构造器
function Person(firstName) {
  this.firstName = firstName;
}

// 在Person.prototype中加入方法
Person.prototype.walk = function(){
  alert("I am walking!");
};
Person.prototype.sayHello = function(){
  alert("Hello, I'm " + this.firstName);
};

// 定义Student构造器
function Student(firstName, subject) {
  // 调用父类构造器, 确保(使用Function#call)"this" 在调用过程中设置正确
  Person.call(this, firstName);

  // 初始化Student类特有属性
  this.subject = subject;
};

// 建立一个由Person.prototype继承而来的Student.prototype对象.
// 注意: 常见的错误是使用 "new Person()"来建立Student.prototype.
// 这样做的错误之处有很多, 最重要的一点是我们在实例化时
// 不能赋予Person类任何的FirstName参数
// 调用Person的正确位置如下,我们从Student中来调用它
Student.prototype = Object.create(Person.prototype); // See note below

// 设置"constructor" 属性指向Student
Student.prototype.constructor = Student;

// 更换"sayHello" 方法
Student.prototype.sayHello = function(){
  console.log("Hello, I'm " + this.firstName + ". I'm studying " + this.subject + ".");
};

// 加入"sayGoodBye" 方法
Student.prototype.sayGoodBye = function(){
  console.log("Goodbye!");
};

// 测试实例:
var student1 = new Student("Janet", "Applied Physics");
student1.sayHello();   // "Hello, I'm Janet. I'm studying Applied Physics."
student1.walk();       // "I am walking!"
student1.sayGoodBye(); // "Goodbye!"

// Check that instanceof works correctly
console.log(student1 instanceof Person);  // true
console.log(student1 instanceof Student); // true

对于“Student.prototype = Object.create(Person.prototype);”这一行,在不支持 Object.create方法的老JavaScript引擎中,
可以使用类似下面的代码来进行解决:

function createObject(proto) {
    function ctor() { }
    ctor.prototype = proto;
    return new ctor();
}

// Usage:
Student.prototype = createObject(Person.prototype);


链接: https://www.fly63.com/article/detial/3521

JavaScript 函数式编程

我理解的 JavaScript 函数式编程,都认为属于函数式编程的范畴,只要他们是以函数作为主要载体的。

Js函数式编程,给你的代码增加一点点函数式编程的特性

给你的代码增加一点点函数式编程的特性,最近我对函数式编程非常感兴趣。这个概念让我着迷:应用数学来增强抽象性和强制纯粹性,以避免副作用,并实现代码的良好可复用性。同时,函数式编程非常复杂。

让我们来创建一个JavaScript Wait函数

Async/await以及它底层promises的应用正在猛烈地冲击着JS的世界。在大多数客户端和JS服务端平台的支持下,回调编程已经成为过去的事情。当然,基于回调的编程很丑陋的。

JavaScript函数创建的细节

如果你曾经了解或编写过JavaScript,你可能已经注意到定义函数的方法有两种。即便是对编程语言有更多经验的人也很难理解这些差异。在这篇博客的第一部分,我们将深入探讨函数声明和函数表达式之间的差异。

编写小而美函数的艺术

随着软件应用的复杂度不断上升,为了确保应用稳定且易拓展,代码质量就变的越来越重要。不幸的是,包括我在内的几乎每个开发者在职业生涯中都会面对质量很差的代码。这些代码通常有以下特征:

javascript回调函数的理解和使用方法(callback)

在js开发中,程序代码是从上而下一条线执行的,但有时候我们需要等待一个操作结束后,再进行下一步操作,这个时候就需要用到回调函数。 在js中,函数也是对象,确切地说:函数是用Function()构造函数创建的Function对象。

js调用函数的几种方法_ES5/ES6的函数调用方式

这篇文章主要介绍ES5中函数的4种调用,在ES5中函数内容的this指向和调用方法有关。以及ES6中函数的调用,使用箭头函数,其中箭头函数的this是和定义时有关和调用无关。

JavaScript中函数的三种定义方法

函数的三种定义方法分别是:函数定义语句、函数直接量表达式和Function()构造函数的方法,下面依次介绍这几种方法具体怎么实现,在实际编程中,Function()构造函数很少用到,前两中定义方法使用比较普遍。

js在excel的编写_excel支持使用JavaScript自定义函数编写

微软 称excel就实现面向开发者的功能,也就是说我们不仅可以全新定义的公式,还可以重新定义excel的内置函数,现在Excel自定义函数增加了使用 JavaScript 编写的支持,下面就简单介绍下如何使用js来编写excel自定义函数。

js中的立即执行函数的写法,立即执行函数作用是什么?

这篇文章主要讲解:js立即执行函数是什么?js使用立即执行函数有什么作用呢?js立即执行函数的写法有哪些?

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!