JavaScript学习笔记十一

105 阅读19分钟

eleven

一:原型链

1.1 探究 --- 构造函数实例化后,原型对象和实例化对象。

代码范例一:观察打印结果

function Car(){} //一个空的 Car 的自定义构造函数
var car = new Car(); //实例化对象
console.log(Car.prototype); //打印 Car 自定义构造函数的原型对象
console.log(car); //打印实例化对象

代码范例一图解: image-20231222150512604.png 代码范例二:

//验证 prototype 与 __proto__ 的关系
console.log(Car.prototype === car.prototype) //打印结果:false,为什么?
console.log(Object.getPrototypeOf(car) === Car.prototype); //打印结果:true,等价于第4行代码
console.log(Car.prototype === car.__proto__) //打印结果:true
console.log(Car.prototype.__proto__ === car.__proto__.__proto__) //打印结果:true

解释 上述代码:

​ 在 JavaScript 中,每个实例化对象都有一个 __proto__ 属性,该属性指向其构造函数的原型对象。而构造函数本身有一个 prototype 属性,这个属性

指向新创建的实例对象的原型。因此,Car.prototype 指向 Car 构造函数的原型对象,因此 car.__proto__ 指向 Car.prototype ,两个完全相等 。

​ 代码第二行中,car.prototype 这个表达式是错误的。所有对象包括实例化对象 car 都没有名为 prototype 的属性,但,Car.prototype 一旦设置

了属性或方法,实例化对象 car 是可以继承原型上的属性与方法,也意味着就可以使用 car.prototype.xx的方法 。

​ 代码第三行中,Object.getPrototypeOf(car) 来获取 car 的原型。Object.getPrototypeOf() 等价于 car.__proto__ ,虽然 __proto__ 在实

践中使用广泛,但 ECMAScript 262 中推荐使 Object.getPrototypeOf()Object.setPrototypeOf() 来获取和设置对象的原型。

总结:

  • Car.prototypeCar 构造函数的原型对象。
  • car.__proto__Object.getPrototypeOf(car)car 实例的原型对象,它指向 Car.prototype
1.2 探究 --- 原型链继承

代码范例三:展示原型链与继承

Professor.prototype.tSkill = 'JAVA'; //在 Professor 构造函数的原型对象上挂载一个 tSkill 属性,值为 'JAVA' 
function Professor(){} //创建一个名为 Professor 的空自定义构造函数
var professor = new Professor(); //实例化对象 professor

Teacher.prototype = professor; //把 professor 对象赋值给 teacher 原型对象,Teacher 的实例将继承 professor 的原型对象上的属性和方法。
function Teacher(){ //创建一个名为 Teacher 的空自定义构造函数
    this.mSkill = 'JS/JQ'; //创建 mSkill 属性,值为 JS/JQ
}
var teacher = new Teacher(); //实例化对象 teacher

Student.prototype = teacher; //把 teacher 对象赋值给 student 原型对象, Student 的实例将继承 teacher 的原型对象上的属性和方法。
function Student(){ //创建一个名为 Student 的空自定义构造函数
    this.pSkill = 'HTML/CSS' //创建 pSkill 属性,值为 HTML/CSS
}
var student = new Student(); //实例化对象 student
//此时的 student 就已经继承了 teacer 和 professor 对象的属性与方法,以及相应的原型
console.log(student); //HTML/CSS
//既然继承了上述对象的属性与方法,以及相应的原型。那么就可以自由的访问相应的属性与方法,当然,这里没有写方法。
console.log(student.mSkill); //JS/JQ
console.log(student.tSkill); //JAVA

image-20231223185730110.png

image-20231226034713981.png

结论

原型链:

在 JavaScript 中,每个对象(null 除外)都有一个原型对象,而原型对象也可以拥有自己的原型对象,这样就形成一个链式结构(套娃),这就是原型链。

  • 对象原型:每个对象(除了 null)都有原型对象,通过 __proto__ 属性(ES6 推荐使用 Object.getPrototypeOf() 方法)访问;
  • 原型也是对象: 原型本身也是对象,因此它也有自己的原型。这样一层层地链接下去,形成了原型链;
  • 原型链的终点: 原型链的终点是 Object.prototypeObject.prototype 是 JavaScript 中所有对象的最顶层的原型,它是原型链的起点和终点。所有的对象最终都会通过原型链连接到 Object.prototype,形成一个链条。
  • 原型链的最终终点: 在原型链中,对象的原型通过 __proto__Object.getPrototypeOf() 指向其构造函数的 prototype,而 Object.prototype 的原型是 null。所以说,null 是原型链的最终终点。验证这一说法,console.log(Object.prototype.__proto__) ,结果为 null

Object.prototype 的原型为 null 有以下几个原因:

  • 它允许我们在对象上添加新的属性和方法,而不会影响其他对象;
  • 它允许我们在对象上重写原有的属性和方法;
  • 它允许我们在对象上定义私有属性和方法。

原型链继承:

原型链继承是 JavaScript 中一种对象之间实现继承关系的方式。在这种模式中,一个对象可以通过继承另一个对象的属性和方法以及原型,形成一个原型链。

1.3 探究 --- 原型链继承关系上的增删改操作

问:为什么标题中没有查?

答:实际上,在原型链继承中,查询和访问是等效的操作。在代码示例三中已经验证了这一点,所以为了简洁,这里不再赘述。

问:在原型链继承关系中,操作子对象是否可以新增或修改父对象或祖先对象中的内容?

答:可以,在操作层面,是有方法来实现。

问:删除与更改的关系?

答:删除特定的属性实际上意味着移除该属性,而更改则是指修改属性的值。如果不完全删除属性名,那么所做的更改实际上是对属性值的修改。

问:那如何删除?

答:直接操作实例化对象是不可行,只有在构造函数的原型上操作才可以。

问:引用类型与原始类型在原型链继承关系中的行为区别?

答:当引用类型的值在原型链上被更改时,这种更改会在所有继承自该原型的实例中同步体现,因为它们共享同一引用。而原始类型的值在子对象中被更改

时,这种更改仅影响该子对象,不会影响父级或祖先级的原型对象。

关于增删改的结论:

​ 总体而言,在操作层面,原型链上的增加、删除和修改操作都是可行的。然而,在实际的开发实践中,特别是当多个实例已经创建并且共享相同原型时,

强烈建议不要轻易进行这些操作。修改原型链可能会导致程序行为变得难以预测和维护。因此,在处理原型链继承关系上的增删改操作时,需要极为谨慎,以

避免引入潜在的错误和不稳定因素。具体实践请看代码范例四。

代码范例四:验证上述内容

Professor.prototype.tSkill = 'JAVA';
function Professor(){}
var professor = new Professor();
Teacher.prototype = professor;
function Teacher(){
    this.mSkill = 'JS/JQ';
    this.success = {alibaba: '28', tencent: '30'};
}
var teacher = new Teacher();
Student.prototype = teacher;
function Student(){
    this.pSkill = 'HTML/CSS';
}
var student = new Student();

//在原型链继承关系中,可以通过 __proto__ 属性,逐层向上查询父级或祖级实例化对象内容及原型
//特别注意:要实现下面这个功能,必须是原型链继承关系,且实例化对象相互逐层向下继承才行
//console.log(student.__proto__); //打印 teacer
//console.log(student.__proto__.__proto__); //打印 professor

// ------------------------------ 按需开启或关闭注释 -------------------------------
//实验1:
//目的1:在原型链继承关系中,操作子对象是否可以新增或修改父对象或祖先对象中的内容?
//目的2:观察在子级对象中修改的同时,原型链继承关系上的实例化对象是否都会发生改变?
//目的3:演示在原型链继承关系上的删除以及影响。

//尝试操作子对象新增祖级对象中的内容
//student.__proto__.__proto__.profInfo = { name: 'Tom', age: 45}
//console.log(professor, teacher, student)

//尝试操作子对象新增父级对象中的内容
//student.__proto__.teacherInfo = { name: 'smith', age: 25}
//console.log(professor, teacher, student)

//尝试修改祖级对象,有两种方式
//一:通过 __proto__ 属性来修改
//student.__proto__.__proto__.profInfo.age = 46;
//二:通过对象的 点语法 来进行修改
//student.profInfo.age = 46;

//观察各个对象是否发生变化
//console.log(professor, teacher, student);

//在原型链继承关系中,删除原型对象中的属性,无论是引用值或原始值,且无论是实例化前后,所产生的影响都是一样
//delete Professor.prototype.tSkill;
//delete Student.prototype.success;
//console.log(professor, teacher, student);
// ------------------------------  按需开启或关闭注释 -------------------------------

// ------------------- 实验原始类型与引用类型开始 按需开启或关闭注释 -------------------
//实验2:
//目的1:观察引用类型与原始类型在原型链继承关系中的行为(改)区别?

//修改引用值
//student.teacherInfo.age = 23;

//结果
//console.log(student);

//修改原始值
//student.students++;

//结果
//console.log(student);
// ------------------- 实验原始类型与引用类型结束 按需开启或关闭注释 -------------------

图解打印结果: image-20231227035954188.png image-20231227040017064.png

image-20231227040103558.png

1.4 探究 --- this 指向问题

问:在 代码范例五 中,为什么打印的是 Benz ,而不是 Audi 呢?

答:这就涉及到 this 指向问题。展开如下:

1:首先,构造函数的原型对象中,挂载了 brand 属性和 intro 方法;

2:在 intro 方法内,使用的是 this ,我们的常识告诉我们, this 是谁调用就指向谁;

3:明白 this 指向的问题,我们在来看 car.intro()

4:代码的第 10 行,创建了一个实例化对象 car ,然后在 11 行中,调用 intro 方法;

5:在调用 intro 方法时,首先在 car 对象内寻找,如果有,就立即调用其方法,如果没有,则向上寻找,这个向上就是去原型对象内查找;

6:在原型对象内查找到有一个 intro 方法,则立即调用,发现 intro 的方法内还有一个 this.brand 的指向;

7:因为已经实例化对象,且对象内有 brand 这个属性,因此 this.brand 就指向实例化对象内的 brand ,所以打印的是 Benz 而不是 Audi。

代码范例五:

function Car(){
    this.brand = 'Benz';
}
Car.prototype = {
    brand: 'Audi',
    intro: function(){
        console.log('我是' + this.brand + '车');
    }
}
var car = new Car();
car.intro(); // 我是 Benz 车

//如果想打印 Audi ,有两种方法。
//1:修改 intro 方法内的 this 指向
//intro: function(){ console.log('我是' + Car.prototype.brand + '车')};
//2:直接使用构造函数调用,而不是修改 intro 方法内的 this 指向
//Car.prototype.intro();
1.5 探究 --- 普通函数与构造函数的返回

普通函数 ---> 如果没有写返回值,则默认是返回 undefined ;

构造函数 ---> 如果没有写返回值,则默认是返回 this ;

  • 构造函数内,使用 return 关键字(指令) 返回 原始值,在实例化后,还是默认返回 this
  • 构造函数内,使用 return 关键字(指令) 返回 引用值,在实例化后,则返回的是 引用值

代码范例六:

//普通函数,没有写返回值 return
function normalFunction(){
    var x = 0;
};
// 调用普通函数
var result1 = normalFunction();

//构造函数
function ConstructorFunction() {
  this.value = 'Some value'; // 设置实例的属性
  //没有返回语句,所以默认返回 this
}
// 使用 new 关键字构造函数
var result2 = new ConstructorFunction();

//打印普通函数和构造函数的结果
console.log(result1, result2);
//Print Result:undefined  ►ConstructorFunction {value: 'Some value'}
//Expalanation:
//1:normalFunction 是一个普通函数,它内部没有 return 语句。当调用这个函数时,它会默认返回 undefined;
//2:ConstructorFunction 没有 return 语句。所以当使用 new ConstructorFunction() 创建一个新实例时,它会默认返回 this,即新创建的对象实例。

二:对象继承

2.1 创建对象的一般常识1

对象字面量:var obj1 = {};

系统内置的构造函数:var obj2 = new Object();

​ 一般情况下,在大多数公司内前端开发中,不推荐或有明文规定不允许使用 系统内置的构造函数 创建对象。原因是,两者无任何区别,但后者跟前者相

对比时,前者可读性、易用性、简洁性,都是后者无法可比拟的。所以,一般都使用对象字面量,来直接创建对象。

2.2 创建对象的一般常识2

​ 通过打印图解,可以看出,前两个对象的原型下面的构造器指向的是系统构造函数,即 constructor: ƒ Object() 。而自定义构造函数,实例化对象

后,其构造器是指向自定义构造函数 constructor: ƒ Obj()

代码范例七:

//对象字面量
var obj1 = {};
console.log(obj1);

//系统内置的构造函数 Object()
var obj2 = new Object();
console.log(obj2);

//自定义构造函数
function Obj3(){}
var obj3 = new Obj3();
console.log(obj3);

打印图解: image-20231228014325380.png

2.3 探究 --- Object.create() 初识1

基本参数:

Object.create() 方法,一共有两个参数,第一个参数必选,第二个参数为可选。

第一个参数:可以是 对象null

第二个参数:目前来说,还没有到接触的时候,暂且不表。

它的功能:

以一个现有对象作为原型,创建一个新对象。

  • 进一步解读:

创建一个新对象,并将这个新对象的原型设置为另一个已存在的对象。这意味着新创建的对象将继承现有对象的属性和方法。

  • 在进一步解读:

Object.create() 的主要作用是实现对象的原型继承,这与使用构造函数创建对象,并通过原型链继承属性和方法有着相同的效果。但

Object.create() 提供了一种更加直观且字面的方式来设置对象的原型。

代码范例八:验证上述内容

//常规MDN例子 --- 进行了一定的改写
var person = { //使用对象字面量声明一个对象
    isHuman: false,
    printIntroduction: function(){
        //一个新的技巧:
        //当一个对象作为即将被设置成另一个对象的原型时,在一个方法内,是可以提前使用 this 关键字指向一个未被在当前的对象中定义的属性名;
        //当新对象被实例化后,其作为原型的对象中的 this.name 属性就指向了当前的新对象,意味着可以进行赋值操作,在后续使用中,就可以被正常的调用。
        console.log('My name is' + ' ' + this.name + '.' + ' ' + 'Am I human?' + this.isHuman); 
    }
}; 
var me = Object.create(person); //使用 Object.create() 方法,创建一个对象并设置对象的原型,也意味这个新对象继承了 person 对象的属性与方法。
me.name = 'Smith'; //创建 me 对象的属性并赋值
me.isHuman = true; //继承的属性是可以重写
me.printIntroduction();
//Print Result:My name is Smith. Am I human?true

//原型,同样也是一个对象,作为参数传入
function Obj(){}
Obj.prototype.name = 'test';
//传参,构造函数的 prototype 也是一个对象,当然也可以作为一个参数传入进去
var newObj1 = Object.cerate(Obj.prototype);
var newObj2 = new Obj();
console.log(newObj1);
console.log(newObj2);
//上述打印的两个结果都一样,只是两个不同的对象而已
2.4 探究 --- Object.create() 初识2

问:为什么在控制台中查看对象,发现里面什么也没有,这是为什么?

答:因为,使用 Object.create(null) 创建一个对象时,它不继承自 Object.prototype 。这意味着该对象不仅没有标准的对象方法(如 toString

hasOwnProperty),而且它实际上也没有原型链(即没有 __proto__ 属性或内部 [[Prototype]] 链接)。

代码范例九:一无所有的空对象

//创建 obj1 空对象
var obj1 = Object.create(null);
console.log(obj1);

打印图解:

image-20231229014156247.png

代码范例十:改造空对象

//创建 obj1 空对象
var obj1 = Object.create(null);
console.log(obj1);
//增加属性并赋值
obj1.num = 1;
//实例化一个对象,并指定另一个对象作为,实例化对象的原型
//继承关系
var obj2 = Object.create(obj1);
console.log(obj2);

打印图解:

image-20231229020922011.png

代码范例十一:自造的 __proto__ 与 系统的 __proto__ 的区别

//创建 obj 空对象
var obj = Object.create(null);
//obj 对象添加一个属性与值
obj.num = 1;
//使用对象字面量声明一个对象,并创建一个属性以及赋值
var obj1 = {count: 2};
//尝试手动给 obj 对象创建一个 __proto__ 属性,并在其中挂载一个 obj1 对象
obj.__proto__ = obj2;

//进行测试
//如果可以访问到 count 的值,则能证明,可以通过手动设置 __proto__ 的方式,来指定一个原型
console.log(obj1.count); //undefined
//打印结果,证明这个方法不可行
//也就说明,Object.create(null) 创建的对象,不支持手动设置 __proto__ 属性,且也没有 __proto__ 属性。
//同时也说明,__proto__必须是系统内置的。
//可以更改系统内置的 __proto__ 的前提是对象必须有 __proto__ 属性。但是我们不能无中生有。
2.5 跑不掉的 undefined 与 null

问:undefined 与 null 是否有 toString() 方法,为什么?

答:个人理解有点长,展开如下:

  • 在 JavaScript 中,undefined 和 null 是属于原始类型,并不是引用类型,也就不是对象,如果是对象就会继承 Object.prototype 中的 toString() 方法;
  • 同时,原始类型中除了 undefined 和 null 之外,其它的都可以通过包装类,临时转换为对象,也就有了 toString() 方法;
  • 需要了解的一个知识,包装类中的 toString() 方法,并不是继承了 Object.prototype 中的 toString() 方法,而是重新改写了这个方法。

代码范例十二:验证上述内容的一小部分

console.log(undefined.toString());
console.log(null.toString());
//Print Result: Uncaught TypeError: Cannot read properties of undefined (reading 'toString')
//Translate to Chinese:无法读取未定义的属性(读取 "toString")
2.6 验证 Object.create(null) 中是否有 toString() 方法?

通过下方的代码范例,可以很明确的看到,是没有的。

问:为什么没有?

答:因为 Object.create(null) 实例化对象时,并没有继承 Object.prototype 中定义的任何方法。

Tips:

​ document.write() 方法中,接收一个字符串参数,并将这个字符串作为 HTML 标签或文本写入到 HTML 文档中。所以,任何类型的数据作为参数传入进

去时,都会隐式的转换为字符串,如果不能转换,则会报错。

代码范例十三:

var num = 1;
var obj = {};
var obj2 = Object.create(null);
//document.write 隐式转换为 字符串
document.write(num);// 1
document.write(obj);// [object Object]
document.write(obj2);// Cannot convert object to primitive value
//Translate to Chinese:不能将对象转换为原始值

//手动写一个 toString()
//可以这么写,但是无意义
obj2.toString = function(){
    return 'hello world!';
}

//验证下,但需要把前面的 obj2 给注释掉才行
document.write(obj2.toString()); //hello world!
2.7 认识包装类中的 toString() 方法的重写

在 JavaScript 中,数据类型的 toString() 方法,并不是直接继承 Object.prototype 中的 toString() 方法,而是重写。

代码范例十四:验证上述内容

//第一步
//观察下面的打印结果
console.log(Object.prototype);
console.log(Number.prototype);
console.log(String.prototype);
console.log(Boolean.prototype);
console.log(Array.prototype);
//Print Result:每个类型的原型中都包含一个 toString() 方法

//第二步
//观察控制台中的每一行打印结果
Object.prototype.toString.call(1);
Object.prototype.toString.call('a');
Object.prototype.toString.call(true);
//Print Result:结果以字符串的形式显示,'[object Number]' '[object String]' '[object Boolean]'
//Explanation:对象的数字类型构造函数 对象的字符串类型构造函数 对象的布尔类型构造函数

//第三步
Number.prototype.toString.call(1);
String.prototype.toString.call('a');
Boolean.prototype.toString.call(true);
Array.prototype.toString.call([1, 2, 3]);
//Print Result:结果以字符串的形式显示,'1' 'a' 'true' '1,2,3'
//Explanation:通过上述三步,可以得知:
//1、在对象的原型顶端原型中 toString() 方法,显示的结果是以字符串的形式显示 '[object Type]' 其中 Type 会被视为相应的数据类型;
//2、在对应的数据类型中的 toString() 方法,显示的结果是以字符串的形式展示数据内容,而不是数据类型;
//3、在 JavaScript 中,数据类型的 toString() 方法,并不是直接继承 Object.prototype 中的 toString() 方法,而是重写。

三:call 与 apply 方法的初步认识

基本概念:

​ 在 JavaScript 中,call, apply, 和 bind 都是 Function.prototype 上的方法,用于控制函数的调用过程。它们允许你设置函数执行时的 this 上下

文(即函数内部的 this 值)以及传递参数。尽管它们的功能相似,但它们在如何接收参数和具体用法上有所不同。

3.1 call

Function 实例的 call() 方法会以给定的 this 值和逐个提供的参数调用该函数。

语法:

  • call(thisArg)
  • call(thisArg, arg1, /* …, */ argN)

参数:

  • thisArg --- 必选,在调用 func 时要使用的 this 值。
    • 个人目前的理解,这个参数,是一个对象,或者是 this 指向,当然是需要根据当前代码执行期的上下文理解。
  • arg, ..., argN --- 可选,参数。

代码范例十五:验证上述内容

//call简单示例一
function Greet() { //声明了一个自定义构造函数
    //等待 this 指向
    console.log(this.animal, "的睡眠时间一般在", this.sleepDuration, "之间");
}
var obj = { //对象字面量
    animal: "猫",
    sleepDuration: "12 到 16 小时",
};

Greet.call(obj); //利用 call 方法动态更改函数执行期上下文 this 指向
//Print Result:猫 的睡眠时间一般在 12 到 16 小时 之间
//Explanation: 执行时的个人理解
//在执行 Greet.call(obj) 时,Greet 函数的上下文 this 指向被显式的设置为 obj 对象。因此,Greet 函数内的 this.animal 和 this.sleepDuration 分别指向了 obj 对象的 animal 和 sleepDuration 属性名与值。因此打印内容为预期结果。

//call简单示例二
function Car(brand, color) { //声明了一个自定义构造函数
    this.brand = brand; //初始化实例化对象的属性名与值
    this.color = color; //同上
}
var Car1 = {}; //使用对象字面量的方式 声明一个空对象
var Car2 = {}; //同上
Car.call(Car1, 'Benz', 'red'); //利用 call 方法动态更改函数执行期上下文 this 指向
Car.call(Car2, 'Audi', 'black'); //同上
console.log(Car1, Car2); 
//Print Result:{ brand: 'Benz', color: 'red' } { brand: 'Audi', color: 'black' }
//Explanation:与 call 简单示例一不同之处就传参。因此打印内容为预期结果。
3.2 apply

Function 实例的 apply() 方法会以给定的 this 值和作为数组(或类数组对象)提供的 arguments 调用该函数。

语法:

  • apply(thisArg)
  • apply(thisArg, argsArray)

参数:

  • thisArg --- 必选,在调用 func 时要使用的 this 值。
    • 个人目前的理解,这个参数,是一个对象,或者是 this 指向,当然是需要根据当前代码执行期的上下文理解。
  • apply(thisArg, argsArray)--- 可选,传入一个数组。

代码范例十六:验证上述内容

//apply 简单示例
function Car(brand, color) { //声明了一个自定义构造函数
    this.brand = brand; //初始化实例化对象的属性名与值
    this.color = color; //同上
}
var Car1 = {}; //使用对象字面量的方式 声明一个空对象
var Car2 = {}; //同上
Car.apply(Car1, ['Benz', 'red']); //利用 apply 方法动态更改函数执行期上下文 this 指向
Car.apply(Car2, ['Audi', 'black']); //同上
console.log(Car1, Car2); 
//Print Result:{ brand: 'Benz', color: 'red' } { brand: 'Audi', color: 'black' }
//Explanation:与 call 唯一的区别,就是传参是数组而已,目前的认知。因此打印内容为预期结果。
3.3 this 的指向改变,应用场景初步认识

call()apply() ,这两个动态的改变函数内的 this 指向,在使用上一般常用的是 apply() 方法。

为什么要用 动态的改变函数内的 this 指向 功能?

  • 扩展方法,借用其他构造函数的方法和属性;

  • 常规的企业开发中,多人协同写一个大的功能,进行分组开发,最终汇总调用;

  • 不同的类型分开写,然后再汇总。

代码范例十七:验证上述内容

// 加减乘除功能演示
function Count(){ // Count 自定义构造函数定义了两个基本的数学运算方法:加法和减法。
    // 加法运算:返回两个数的和。
    this.plus = function(a, b){
        return a + b;
    }
    // 减法运算:返回两个数的差。
    this.minus = function(a, b){
        return a - b;
    }
}

function FullCount(){ // FullCount 自定义构造函数继承 Count 并添加了乘法和除法操作。
    // 使用 apply() 方法,继承 Count 自定义构造函数的方法,使得 FullCount 实例也具有 plus 和 minus 方法。
    Count.apply(this);
    // 乘法运算:返回两个数的乘积。
    this.multiplied = function(a, b){
        return a * b;
    }
    // 除法运算:返回两个数的商。
    this.div = function(a, b){
        return a / b;
    }
}

var fullCount = new FullCount(); // 创建 FullCount 的实例。
console.log(fullCount.plus(2, 3)); //输出: 5
console.log(fullCount.minus(5, 3)); // 输出: 2
console.log(fullCount.multiplied(9, 3)); // 输出: 27
console.log(fullCount.div(7, 3)); // 输出: 2.3333333333333335