深入显出JavaScript函数,面向对象学习笔记_底子知识_脚本之家

断言

本文的观点是建立在《JavaScript权威指南 6th Ed》 《JavaScript高级编程 3th
Ed》 《JavaScript精粹 2th Ed》之上,

单元测试框架的核心是断言方法,通常叫assert()。

笔者将尽所能将概念、原理叙述清楚,由于笔者水平有限,理解不当之处在所难免,请读者不吝交流。

该方法通常接收一个值–需要断言的值,以及一个表示该断言目的的描述。


如果该值执行的结果为true,断言就会通过;


否则,断言就会被认为是失败的。

目录

1 什么是函数?

深入显出JavaScript函数,面向对象学习笔记_底子知识_脚本之家。2 函数就是对象!

3
函数字面量(函数表达式)

4 函数调用

4.1 方法调用模式

4.2 函数调用模式

4.3
构造器调用模式

金沙网址,4.4
间接调用模式

5
函数的参数与返回值

6 扩充类型的功能

7 递归函数

8 闭包

9 模块模式

10 级联(方法链) 

11 记忆

12 函数拾遗

在本文中,你将学到的概念:“调用”属性、匿名函数、函数声明提升、实参对象、扩充类型功能、嵌套函数、方法链、闭包、模块模式、记忆

(这些概念均来自以上提到的三本书,这里列出来的目的是给读者提供索引。)

引用《JavaScript精粹》的一句话:JavaScript设计的最出色的的就是它的函数的实现,它几乎接近完美。

通常用一个相应的通过标记记录相关的信息;

什么是函数?

函数是一组语句,是JavaScript的基础模块单元,用于代码复用、信息隐藏和组合调用。

函数默认的返回值是undefined值。

函数用于指定对象的行为(作为对象的方法)。

function assert { let li = document.createElement; li.className = value ? 'pass' : 'fail'; li.appendChild(document.createTextNode; document.getElementById.appendChild;}// 断言函数function assert { if  { console.log(`33[32m ${desc} 33[0m`); // 断言通过 绿色字体 } else { console.log(`33[31m ${desc} 33[0m`); // 断言失败 红色字体 }}

函数就是对象!

对象是“key/value”对的集合并拥有一个连接到原型对象的“指针”。(对象字面量产生的对象连接到Object.prototype,

函数对象连接到Function.prototype)【关于原型对象的描述,我将在以后的博文中分享给大家,到时候会在此给出链接】。

每个函数在创建的时候会附加两个隐藏属性:函数的上下文(this)和实现函数行为的代码(“调用”属性JavaScript函数调用的时候,就是调用了此函数的“调用”属性,这是函数与众不同的地方,可以被调用)

由于函数是对象,所以函数可以出现在对象能出现的任何位置(保存在变量、数组、对象中),还可以作为参数传递给其他函数,也可以作为其他函数的返回值。

而且,函数也可以拥有方法。

函数

函数字面量(函数表达式)

函数对象通过函数字面量来创建:

1 // 创建一个名为 add 的变量,并用来把两个数字相加的函数赋值给它。
2 var add = function /*optional name*/ (a,b) {
3     return a + b;
4 } ; //注意结尾的分号

函数字面量可以出现在任何允许表达式出现的地方,也可以定义在其他函数中。

函数字面量包括四个部分:

  • 第一部分是保留字 function。
  • 第二部分是函数名,可选。主要用于函数递归(很好理解,函数得用它的名字递归调用自己吧),还能用来被调试器和开发工具来识别函数。如果没有给函数命名,则称为匿名函数。
  • 第三部分是包围在圆括号中的一组参数,多个参数用逗号隔开(称为参数表达式)。参数的名称被定义为函数的变量,不像普通变量被初始化为undefined,而是在函数调用的时候初始化为实参的值。
  • 第四部分是包围在花括号中的一组语句,是函数的主题,在函数被调用的时候执行。

tips: 函数字面量(函数表达式) 和函数声明的区别:

JavaScript解析器会率先读取函数声明,并使其在执行任何代码之前可用(函数声明提升);至于函数表达式,则必须等到解析器执行到它所在的代码行,才会真正的被解释执行。

举例说明:

alert (add (2,3));    //5
function add(a,b) {
    return a+b;
}    // 函数声明提升
//=====为了方便,笔者写在了一起,在测试的时候,可不要在一个作用域中执行哟===============
alert (add (2,3));    //error
var add = function (a,b) {
    return a+b;
}; //函数字面量,注意结尾的分号哟(细节很重要)。

 另外,函数不能(有些浏览器可以,但是不建议)再if while for
等代码块里声明(特指函数声明哟),但是函数字面量可以出现在以上代码块中。

 最后,函数字面量如果有函数名的话,则函数名在外部不可用。

举例说明:

var add = function add1 (a,b) {
    return a+b;
};
add (2,3);    // 返回5
add1(3,4);    //ReferenceError: add1 is not defined

JavaScript是一门函数式语言
在JavaScript中,函数是第一型对象。函数可以共处,可以视作为其他任意类型的对象。就像普通的JavaScript数据类型,,函数可以被任意变量进行引用,或声明成对象字面量,甚至可以将其作为函数参数进行传递。
函数是第一型对象 可以通过字面量进行创建。
可以赋值给变量、数组或其他对象的属性。 可以作为参数传递给函数。
可以作为函数的返回值进行返回。 可以拥有动态创建并赋值的属性。
命名一个函数时,该名称在整个函数声明范围内是有效的。如果函数声明在顶层,window对象上的同名属性则会引用到该函数。
所有的函数都有一个name属性,该属性保存的是该函数名称的字符串。匿名函数的name属性值为空。
在JavaScript中,作用域是由function进行声明的,而不是代码块。声明的作用域创建于代码块,但不是终结于代码块

 函数调用

调用一个函数时,除了声明时定义的形式参数,每个函数还接收两个附加的参数:this和arguments。

this的值取决于调用的模式(不同的模式,this的初始化也不一样)。JavaScript中共有4中调用模式:

  • 方法调用模式
  • 函数调用模式
  • 构造器调用模式(构造函数调用)
  • call()和apply()间接调用模式

调用运算符是跟在任何一个产生函数值的表达式之后的一对圆括号(多么凝练的语句啊)。

圆括号内可包含零个或多个用逗号隔开的表达式。每个表达式产生一个参数值。每个参数值被赋予函数声明时定义的形式参数。

JavaScript不对函数的参数个数和参数类型进行检查。

当实参和形参个数不匹配时,不会导致运行时错误。实参如果过多,超出的参数将会被忽略。如果过少,缺失的值将会被替换为undefined。

if  { var x = 123;}alert;

方法调用模式

当一个函数被保存为对象的一个属性时,它就是该对象的一个方法。当一个方法被调用时,this被绑定到该对象。

可以通过点表达式或者下标表达式来调用一个方法。

// 创建myObject 对象, 它有一个value 属性和一个increment 方法
// increment 方法接收一个可选的参数,如果参数不是一个数字,则默认使用数字1

var myObject = {
    value : 0,
    increment : fucntion (inc) {
        this.value += typeof inc ==='number' ? inc : 1;
    }
};

myObject.increment('joke');    // 1 推荐使用
myObject["increment"](3);    // 4

执行代码后,会弹出123,是因为JavaScript在大括号关闭处并没有终止其作用域。

 函数调用模式

当一个函数不是一个对象的属性时,它就是被当做一个函数来调用的:

var sum = add (5,6);    // sum 的值为11

此模式下,this值被绑定到全局对象(在大部分浏览器中该对象是window对象)

变量声明的作用域开始于声明的地方,结束于函数的结尾,与代码嵌套无关。
命名函数的作用域是指声明该函数的整个函数范围,与代码嵌套无关;
对于作用域声明,全局上下文就像一个包含页面所有代码的超大型函数。
所有的函数调用都会传递两个隐式参数:argument和this

 构造器调用模式

如果在一个函数前面带上new 关键字来调用,
那么背地里将会创建一个连接到该函数的prototype 成员的新对象。

同时,this 会被绑定到那个新对象上。

// 创建一个名为Person 的构造函数,它构造一个带有name 和age 的对象

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

// 给Person的所有实例(就是原型) 提供名为getName() 和getAge() 的方法 

Person.prototype.getName = function (){
    return this.name;
};

Person.prototype.getAge = function () {
    return this.age;
};

// 构造一个Person 实例 ,并测试
var tony = new Person ('tony',23);
console.log('name: '+tony.getName()+' nage: '+tony.getAge());

 一个函数,如果创建的目的就是希望结合new
前缀来调用,那么它就被称为构造器函数

构造器函数按照约定(仅仅是约定哟,但是约定优于配置的思想很重要),首字母都应该大写,这样可以避免调用时丢失new或者new普通函数等错误。

作为函数进行调用

 间接调用模式

JavaScript是一门函数式的面向对象编程语言,函数既然是对象,那么函数可以拥有方法。

其中两个方法 call()  和 apply()
方法可以用来间接的调用函数。两个方法的第一个参数可以绑定函数的this值。

它们的语法如下:

call([thisObj[,arg1[, arg2[, [,.argN]]]]])    // thisObj 是this要绑定的对象,后面是逗号分隔开的参数
apply([thisObj,[arglist]])    //thisObj 是this要绑定的对象,后面是以列表形式的参数。

 举例说明二者的用法:

// 声明一个函数
var add = function (a,b) {
    return a+b;
}; 

// 构造一个含有两个数字的数组
var arr = [5,6];

//===通过apply和call将它们相加===
var sum0 = add.apply(null, arr);
var sum1 = add.call(null,arr[0],arr[1]);

console.log(sum0);
console.log(sum1);

如果一个数不是作为方法、构造器、或者通过apply进行调用的,则认为它是“作为函数”进行调用的。

函数的参数与返回值

在调用函数的时候,除了隐藏的this参数之外,还有arguments参数,它是一个类似于数组的对象。

arguments 拥有一个 length 属性(该属性是可以被改变的哟。),但它没有任何数组的方法。

函数可以通过此参数访问所有传递给函数的参数。

// 定义一个sum函数,它将所有参数进行相加,并返回相加之和
var sum = function (/*可是没形参的哟*/) {
    var sum = 0;
    for (var i = 0; i< arguments.length; ++i) {
        sum += arguments[i];
    }
    return sum;
};

console.log(sum(1,2,3,4,5,6,7));

看上面的例子,如果我们不对传入的参数进行类型检测,当传入的参数不都是Number类型,那么结果将不可预料。

因此,我们要改造sum函数使其能够检测到错误情况。

// 定义一个sum函数,它将所有参数进行相加,并返回相加之和
var sum = function (/*可是没形参的哟*/) {
    var sum = 0;
    for (var i = 0; i< arguments.length; ++i) {
        if( typeof arguments[i] !== 'number' ){
            throw {
                name: 'TypeError',
                message: 'sum needs numbers'
            };
        }
        sum += arguments[i];
    }
    return sum;
};

console.log(sum(1,2,3,4,5,6,7));

try {
    console.log(sum(1,2,'three','4'));
} catch (e) {
    console.log(e.name+' : '+e.message);    // TypeError : sum needs numbers 
}

 一个函数被调用时,从第一句开始执行,并在遇到关闭函数体} 时结束。

但是 return 语句可用来使函数提前返回。
一个函数总是会返回一个值。如果没有指定返回值,则返回undefined。

如果函数调用时前面加上了 new 前缀 ,且返回值不是一个对象,则返回 this
(该新对象)。

function ninjavar samurai = function

扩充类型的功能

JavaScript 允许给语言的基本类型扩充功能。 通过给类型的prototype添加方法,可以扩充该类型对象的功能。

这样的方式适用于函数、数组、字符串、数字、正则表达式和布尔值。

举例说明:

// 通过给Function.prototype 添加方法来使得该方法对所有函数可用
if (! Function.prototype.hasOwnProperty('method')){    // 检测Function原型是否已存在method属性
    Function.prototype.method = function (/*String*/name,       /*function*/func) {
        //首先应该检查参数是否合乎标准
        if (typeof name !=='string' || typeof func !== 'function'){
            // 抛出异常。这里不再赘述
        }
        //其次还要检查name是否已经存在于原型中。
        if (! this.prototype[name]){
            //抛出异常。不再赘述。
        }
        this.prototype[name] = func;
        return this;
    };
}

上例中比较完整的给出了如何扩充类型功能的方法,

通过给Function.prototype 增加一个method
方法,下次给对象添加方法的时候就不用再键入prototype。

首先判断method是否已经存在于原型中,然后将method方法注册到原型。在函数中先检查参数是否合法,再检查name函数已经存在于原型中。

下面就用method 方法注册一个方法到Number原型中(Number
注册method方法和Function类似)。

Number.method('integer', function () {
    return Math[this < 0 ? ' ceil ' : 'floor'] (this);
});

以这种方式调用时,函数的上下文是全局上下文—window对象。

递归函数

递归函数就是会直接或间接地调用自身的一种函数,它将一个问题分解成一组相似的子问题,每一个都用一个寻常解(明显解)去解决。

递归函数可以非常高效的操作树形结构。

使用递归函数计算一个数的阶乘(参见JS 高级程序设计 第三版 p177):

function factorial (num) {
    if (num <=1){
       return 1;
    }else {
        return num* factorial(num -1 );
    }
}

 虽然这个函数表面看起来没什么问题,但下面的代码却能导致它出错。

var anotherFactorial = factorial;
factorial = null;
alert(anotherFactorial (5));    // 出错!

在将factorial设置为null后,再执行anotherFactorial (5)
时,由于必须执行factorial () ,而factorial 以不再是函数,所以导致错误。

那么怎样写出健壮的递归函数呢? 

方法一:
还记得arguments对象吧,该对象有一个属性callee指向正在执行的函数的指针,因此可以用它来实现对函数的递归调用。

function factorial (num) {
    if (num <=1){
       return 1;
    }else {
        return num* arguments.callee(num -1 );
    }
}

方法二(推荐):在严格模式(什么是严格模式?)下,不能通过脚本访问arguments.callee
,访问该属性会导致错误,

不过可以使用函数字面量(命名函数表达式:只不过是没有省略函数名的函数表达式。)来达到相同的结果。

var factorial = (function fact (num) { // 函数名 fact 在外部访问是undefined
    if (num <=1){
        return 1;
    } else {
        return num * fact (num -1);
    }
});

作为方法进行调用

闭包

在说闭包之前,简单说说JavaScript
作用域的问题,JavaScript不支持块级作用域。

if (true) {
    var a = 1;
    console.log(a);    // 1
}
console.log(a);    //1

JavaScript
确实有函数作用域,这意味着定义在函数中的参数和变量在函数外部是不可见的,而在一个函数内部任何位置定义的变量,在该函数内部任何地方都可见。

由于JS缺少块级作用域,所以最好的做法是在函数体的顶部声明所有可能要用到的变量。

作用域的好处是内部函数可以访问定义它们的外部函数的参数和变量(除了this和arguments:将this和arguments赋值给其他变量,可以间接访问到。)

更美好的是,内部函数拥有比他外部函数更长的生命周期。

var myObj = ( function () {
    var value = 0;
    return {
        increment: function (inc) {
            value += typeof inc === 'number' ? inc : 1;
        },
        getValue: function () {
            return value;
        }
    };
  } ()
);

最外部的匿名函数中定义变量value,并返回拥有两个方法的对象,这些方法继续享有访问value变量的特权。该对象不能被非法修改value的值,只能通过两个方法来修改。

两个方法(函数)可以访问它被创建时所处的上下文环境,这就被称为闭包。闭包是指有权访问另一个函数作用域中的变量的函数。

理解内部函数(嵌套函数)能访问外部函数的实际变量,而无须复制是很重要的。

下面引用《JS 精粹 》
p38页的例子来说明:

// 糟糕的例子
// 构造一个函数,用错误的方式给一个数组中的节点设置事件处理程序。
// 当点击一个节点时,按照预期,应该弹出一个对话框显示节点的序号,
// 但它总是会显示节点的数目

var add_the_handlers = function (nodes) {
    var i;
    for (i = 0; i < nodes.length; ++i){
        nodes[i].onclick = function (e) {
            alert(i);  
        };
    }
};    //每一个事件处理函数,都弹出一个对话框显示节点的数目 nodes.length

该函数的本意是想传递给每个事件处理器一个唯一的值(i),但它未能达到目的,因为事件处理器绑定了变量i本身,而不是函数在构造时的变量i的值。(理解了吗?
不理解的话看下一个例子)

// 改良后的例子

// 构造一个函数, 用正确的方式给一个数组中的节点设置事件处理程序,
// 点击一个节点,将会弹出一个对话框显示节点的序号。

var add_the_handlers = function (nodes) {
    var i;
    var helper = function (i) {
        return function (e){
            alert(i);
        };
    };
    for(i = 0; i < nodes.length; ++ i){
        nodes[i].onclick =  helper(i);
    }
};

避免在循环中创建函数,先在循环之外创建一个辅助函数,让这个辅助函数再返回一个绑定了当前i值的函数

当一个函数被赋值给对象的一个属性,并使用引用该函数的这个属性进行调用时,那么函数就是作为该对象的一个方法进行调用的。

模块模式

可以使用函数和闭包来构造模块。模块是一个提供接口却隐藏状态与实现的函数或对象。

通过使用函数产生模块,几乎可以完全摒弃全局变来那个的使用。

模块模式的一般形式是:

  • 一个定义了私有变量和函数的函数;
  • 利用闭包创建可以访问私有变量和函数的特权函数;
  • 最后返回这个特权函数,或者把它们保存到一个可以访问到的地方(变量,数组,或者对象中)。

使用模块模式可以摒弃全局变量的使用,促进了信息隐藏和其他优秀的设计实践。对于应用程序的封装,或者构造其他单例对象,模块模式非常有效。

模块模式可以用来产生安全对象,假定想要构造一个用来产生序列号的对象:

var serial_maker = function () {
    // 返回一个用来产生唯一字符串的对象
    // 唯一字符串由两部分组成:前缀+序列号
    // 该对象包含一个设置前缀的方法和一个设置序列号的方法
    // 还有一个得到字符串的方法
    var prefix = '';
    var seq = 0;
    return {
        setPrefix: function (pre) {
            this.prefix = pre;
        },
        setSeq: function (seq) {
            this.seq = seq;
        },
        gensym: function () {
            return this.prefix+this.seq;
        }
    };
};
var seqer = serial_maker ();
seqer.setPrefix("QAZ");
seqer.setSeq(10011);
console.log(seqer.gensym());

 

var 0 = {};o.whatever = function;

 级联(方法链

有一些方法没有返回值,如果我们让这些方法返回this而不是undefined,就可以启用级联。

在一个级联中,可以单独在一条语句中依次调用同一个对象的很多方法。

举个Ajax类库的例子。 

// 某Ajax类库的级联调用
getElement ('myBoxDiv')
    .move (100,200)
    .width (100)
    .height (200)l;

 

 级联技术可以产生出极富表现力的接口。 //
我觉得级联在某些场合比较好用,但不要滥用。

将函数作为对象的一个方法进行调用时,该对象就变成了函数上下文,并且在函数内部可以以this参数的形式进行访问。
将函数作为构造器进行调用,需要在函数调用前使用new关键字

记忆

 函数可以将先前操作的结果记录在某个对象里,从而避免无所谓的重复运算,这种优化被称为记忆。JavaScript的对象和数组要实现这种优化非常方便。

比如用递归函数来计算Fibonacci 数列:

var fibonacci = (function fib (n) {
    return n < 2 ? n : fib (n-1) + fib (n-2); 
});

for (var i = 0; i <= 10; ++i){
    console.log (fibonacci (i)+ 'n');
}

 

但是这样它做了很多无谓的工作,如果我们让该函数具备记忆功能,就可以显著的减少运算量。

var fibonacci = function () {
    var memo = [0,1];
    var fib = function (n) {
        if (typeof memo[n] !== 'number'){
            memo [n] = fib (n-1) + fib (n-2);
        }
        return memo[n];
    };
    return fib;
}();

for (var i = 0; i <= 10; ++i){
    console.log (fibonacci (i)+ 'n');
}

创建一个新的空对象;

函数拾遗

传递给构造器都对象是this参数,从而成为构造器的函数上下文;

函数没有重载

ECMAScript
函数不能像传统意义上那样实现重载,而其他语言中,可以为一个函数编写两个定义。只要这两个定义(接受的参数的类型和数量)不同即可。

ECMAScript函数没有签名,因为其参数是由包含零个或多个值的“数组”来表示的。而没有函数签名,真正的重载是不能做到的。

如果没有显式都返回值,新创建的对象则作为构造器的返回值进行返回。

模仿块级作用域

匿名函数可以模仿块级作用域,用块级作用域(通常称为私有作用域)
的匿名函数的语法如下:

(function () {
    // 这里是块级作用域
})();
function Ninja() { this.skulk = function() { return this; }}var ninja1 = new Ninja();var ninja2 = new Ninja();

什么是严格模式?

ECMAScript 5 引入了严格模式概念,严格模式是为JavaScript
定义了一种不同的解析和执行模型。在严格模式下,ECMAScript 3
中一些不确定的行为将得到处理,而对某些不安全的操作也会抛出错误。

在整个脚本中启用严格模式,可以在顶部添加如下代码:

"use strict"; 

当然也可以在某一函数中启用严格模式

function doSomething () {
    "use strict"; 
    // 函数体
}
更多内容尽在这里:相关博客一览表

构造器的目的是通过函数调用初始化创建新的对象。

函数调用方式之间点主要差异是:作为this参数传递给执行函数的上下文对象之间点区别。

作为方法调用,该上下文是方法的拥有者;作为全局函数进行调用,其上下文永远是window。作为构造器进行调用,其上下文对象则是新创建的对象实例。

使用apply方法

通过函数的apply传入两个参数:一个是函数上下文的对象,另一个是作为函数参数所组成的数组;
通过函数的call传入两个参数:一个是函数上下文的对象,另一个是作为函数参数的参数列表,而不是单个数组;

function juggle() { var result = 0; for (var n = 0; n < arguments.length; n++) { result += arguments[n] } this.result = result;}var ninja1 = {};var ninja2 = {};juggle.apply;juggle.callassert(ninja1.result === 10, 'juggled via apply');assert(ninja2.result === 26, 'juggled via call');

使用apply可以选择任意对象作为函数上下文;

函数总结

通过字面量进行创建。 赋值给变量或属性。 作为参数进行传递。
作为函数结果进行返回。 拥有属性和方法。

函数是通过字面量进行创建的,其名称是可选的。

在页面生命周期内,浏览器可以将函数作为各种类型的事件处理程序进行调用。

变量的作用域开始于声明处,结束于函数尾部,其会跨域边界

内部函数在当前函数的任何地方都可用,即便是提前引用。

函数的形参列表和实际参数列表的长度可以是不同的。

未赋值的参数被设置为undefined。 多出的参数是不会绑定到参数名称的。

每个函数调用都会传入两个隐式参数。

arguments,实际传入的参数集合。 this,作为函数上下文的对象引用。

可以用不同的方法进行函数调用,不同的调用机制决定了函数上下文的不同。

作为普通函数进行调用时,其上下文是全局对象。
作为方法进行调用时,其上下文是拥有该方法的对象。
作为构造器进行调用时,其上下文是新分配的对象实例。
通过函数的apply方法进行调用时,上下文可以设置成任意值。

匿名函数

为了不让不必要的函数名称污染全局命名空间,可以创建大量的小型函数进行传递,而不是创建包含大量命令语句的大型函数。

递归

递归:当函数调用自身,或调用另外一个函数,但这个函数的调用树中的某个地方又调用到了自己时,就产生了递归。
递归的两个条件:引用自身,并且有终止条件。

闭包

闭包是一个函数在创建时允许自身函数访问并操作该自身函数之外的变量时所创建的作用域
闭包可以让函数访问所有的变量和函数,只要这些变量和函数存在于该函数声明时的作用域内就行。

var outerValue = 'ninja';var later;function outerFunction() { // 该变量的作用域限制在该函数内部,并且在函数外部访问不到; var innerValue = 'samurai'; // 在外部函数内,声明一个内部函数。 // 注意:声明该函数时,innerValue是在作用域内的 function innerFunction() { assert(outerValue, 'I can see the ninja'); assert(innerValue, 'I can see the samurai'); // 将内部函数引用到later变量上,由于later在全局作用域内,所以可以对它进行调用。 later = innerFunction; }}// 调用外部函数,将会声明内部函数,并将内部函数赋值给later变量。outerFunction();// 通过later调用内部函数。// 我们不能直接调用内部函数,因为它的作用域被限制在outerFunction内。later();

闭包使用场景:私有变量

在构造器内隐藏变量,使其在外部作用域不可访问,但是可以存在于闭包内。

function Ninja() { var feints = 0; this.getFenits = function() { return feints; } this.feint = function() { feints++; }}var ninja = new Ninja;assert === 1, '调用一次,内部变量++');assert(ninja.feints === undefined, '函数外部不可访问')

变量的作用域依赖于变量所在的闭包

闭包记住的是变量的引用,而不是闭包创建时刻该变量的值

原型与面向对象

所有的函数在初始化时都有一个prototype属性,该属性的初始值是一个空对象。
使用new操作符将函数作为构造器进行调用的时候,其上下文被定义为新对象的实例。
在构造器内的绑定操作优先级永远高于在原型上的绑定操作优先级。因为构造器的this上下文指向的是实例自身,所以我们可以在构造器内对核心内容执行初始化操作。
通过instanceof操作,可以判断函数是否继承了其原型链中任何对象的功能。

发表评论

电子邮件地址不会被公开。 必填项已用*标注