澳门新葡萄京官网注册【MDNjs笔记】——入门——第三章——对象.构造函数.原型链.继承

介绍

JavaScript
是一个强大的面向对象编程语言,但是,并不像传统的编程语言,它采用一个以原型为基础的OOP模型,致使它的语法让大多数开发人员看不懂。另外,JavaScript
也把函数作为首要的对象,这可能会给不够熟悉这门语言的开发人员造成更大的困惑。那就是我们决定放在前面作为一个简短前言进行介绍的原因,并且在
JavaScript 里也可以用作面向对象编程的一个参考。

这个文档没有提供一个面向对象编程的规则预览,但有它们的接口概述。

时间: 2019-06-03阅读: 257标签: 函数闭包函数

对象Object

由属性property(变量)、方法method(函数)组成

 

var objectName = {

  member1Name : member1Value,

  member2Name : member2Value,

  member3Name : member3Value

}

member(成员)的值是任意的,

 

一个如上所示的对象被称之为对象的字面量(literal)——手动的写出对象的内容来创建一个对象。不同于从类实例化一个对象,我们会在后面学习这种方式。

 

命名空间

随着越来越多的第三方库,框架和web依赖的出现,JavaScript发展中的命名空间是势在必行的,我们得尽量避免在全局命名空间的对象和变量的冲突。

不幸的是,JavaScript没有提供支持命名空间的编译,但是我们可以使用对象来得到同样结果。在JavaScript中我们有许多种模式来实现命名空间接口,但是我们覆盖嵌套的命名空间,它在该领域是最常用的模式。

什么是闭包函数?

访问对象成员

嵌套命名空间

嵌套的命名空间模式使用对象字面量来捆绑一个特定应用的特定名字的功能。

我们最初创建一个全局对象,并且赋值给一个称为MyApp的变量。

// global namespace
var MyApp = MyApp || {};

上述的语法会检查MyApp是否已经被定义过。假如它已经被定义过,我们简单地把它赋值给自己,但是,我们创建一个空的容器来装载我们的函数和变量。

我们也可以使用相同技术来创建子命名空间。例如:

// sub namespaces
MyApp.users = MyApp.user || {};

我们一旦启动我们的容器,我们可以在(容器)内部定义我们的函数和变量,并且在全局命名空间调用它们,不需要冒着与现有定义冲突的风险。

// declarations

MyApp.users = {

    existingUsers: '', // variable in namespace

    renderUsersHTML: function() { // function in namespace

        // render html list of users

    }

};

// syntax for using functions within our namespace from the global scope

MyApp.users.renderUsersHTML();

在JavaScript命名模式的一个内部概述是由Goggle的Addy Osmani在Essential
JavaScript Namespacing
Patterns的文章中介绍的。假如你想探索不同的模式,这里将是一个美好的起点。

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

1.点表示法

对象的名字表现为一个命名空间(namespace),它必须写在第一位——当你想访问对象内部的属性或方法时,然后是一个点(.),紧接着是你想要访问的项目,标识可以是简单属性的名字(name),或者是数组属性的一个子元素,又或者是对象的方法调用。如下所示:

person.age

person.interests[1]

person.bio()

 

对象

如果你写过 JavaScript 代码,那你已经使用过对象了。JavaScript
有三种类型的对象:

function fn1(){ function fn(){ } return fn;}

子命名空间

可以用一个对象来做另一个对象成员的值。例如将name成员

 

 

原生对象

原生对象是语言规范的一部分,不管在什么样的运行环境下运行,原生对象都可用。原生对象包括:Array、Date、Math
和 parseInt 等。想了解所有原生对象,请参阅 JavaScript
内建对象参考

var cars = Array(); // Array is a native object

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

2.括号表示法

person.age

person.name.first

换成:

person[‘age’]

person[‘name’][‘first’]

这看起来很像访问一个数组的元素,从根本上来说是一回事儿,你使用了关联了值的名字,而不是索引去选择元素。难怪对象有时被称之为关联数组(associative
array)
了——对象做了字符串到值的映射,而数组做的是数字到值的映射。(值对)

宿主对象

与原生对象不同,宿主对象是由 JavaScript
代码运行的环境创建。不同的环境环境创建有不同的宿主对象。这些宿主对象在多数情况下都允许我们与之交互。如果我们写的是在浏览器(这是其中一种运行环境)上运行的代码,会有
window、document、location 和 history 等宿主对象。

document.body.innerHTML = 'Hello World!'; // document is a host object

// the document object will not be available in a 
// stand-alone environments such as Node.js

例如:

3.点表示法&括号表示法 差异

点表示法只能接受字面量的成员的名字,不接受变量作为名字。

括号表示法一个有用的地方是它不仅可以动态的去设置对象成员的值,还可以动态的去设置成员的名字。

比如说,我们想让用户能够在他们的数据里存储自己定义的值类型,通过两个input框来输入成员的名字和值,通过以下代码获取用户输入的值:

var myDataName = nameInput.value

var myDataValue = nameValue.value

我们可以这样把这个新的成员的名字和值加到person对象里:

person[myDataName] = myDataValue

 

 

 

 

用户对象

用户对象(或植入对象)是在我们的代码中定义的对象,在运行的过程中创建。JavaScript
中有两种方式创建自己的对象,下面详述。

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

设置对象成员

person.age = 45

person[‘name’][‘last’] = ‘Cratchit’

也可以创建新成员

person[‘eyes’] = ‘hazel’

person.farewell = function() { alert(“Bye everybody!”) }

 

 

对象字面量

在前面演示创建命名空间的时候,我们已经接触到了对象字面量。现在来搞清楚对象字面量的定义:对象字面量是置于一对花括号中的,由逗号分隔的名-值对列表。对象字面量可拥有变量(属性)和函数(方法)。像
JavaScript 中的其它对象一样,它也可以作为函数的参数,或者返回值。

现在定义一个对象字面量并赋予一个变量:

// declaring an object literal

var dog = {

    // object literal definition comes here...

};

向这个对象字面量添加属性和方法,然后在全局作用域访问:

// declaring an object literal

var dog = {

    breed: 'Bulldog', // object literal property

    bark: function() { // object literal method

        console.log("Woof!");

    },

};

// using the object

console.log( dog.breed ); // output Bulldog

dog.bark(); // output Woof!

这看起来和前面的命名空间很像,但这并不是巧合。字面量对象最典型的用法就是把代码封装起来,使之在一个封装的包中,以避免与全局作用域中的变量或对象发生冲突。由于类似的原因,它也常常用于向插件或对象传递配置参数。

如果你熟悉设计模式的话,对象字面量在某种程度上来说就是单例,就是那种只有一个实例的模式。对象字面量先天不具备实例化和继承的能力,我们接下来还得了解
JavaScript 中另一种创建自定义对象的方法。

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

This

关键字”this”指向了当前代码运行时的对象

 

构造函数

例如:

你一直在使用对象

当我们这样使用字符串的方法时:

myString.split(‘,’);

你正在使用一个字符串实例上可用的方法

当你这样访问document对象时:

var myDiv = document.createElement(‘div’);var myVideo
= document.querySelector(‘video’);

你正使用在任一个Document实例上可用的方法。

 

定义构造函数

函数是 JavaScript 一等公民,就是说其它实体支持的操作函数都支持。在
JavaScript
的世界,函数可以在运行时进行动态构造,可以作为参数,也可以作为其它函数的返回值,也可被赋予变量。而且,函数也可以拥有自己的属性和方法。JavaScript
中函数的特性使之成为可以实体化和继承的东西。

来看看怎么用构造函数创建一个自定义的对象:

// creating a function

function Person( name, email ) {

    // declaring properties and methods using the (this) keyword

    this.name   = name;
    this.email  = email;

    this.sayHey = function() {

        console.log( "Hey, I’m " + this.name );

    };

}

// instantiating an object using the (new) keyword

var steve = new Person( "Steve", "steve@hotmail.com" );

// accessing methods and properties

steve.sayHey();

创建构造函数类似于创建普通函数,只有一点例外:用 this 关键字定义自发性和方法。一旦函数被创建,就可以用 new 关键字来生成实例并赋予变量。每次使用 new 关键字,this 都指向一个新的实例。

构建函数实例化和传统面向对象编程语言中的通过类实例化并非完全不同,但是,这里存在一个可能不易被察觉的问题。

当使用 new 关键字创建新对象的时候,函数块会被反复执行,这使得每次运行都会产生新的匿名函数来定义方法。这就像创建新的对象一样,会导致程序消耗更多内存。这个问题在现代浏览器上运行的程序中并不显眼。但随着应用规则地扩大,在旧一点的浏览器、计算机或者低电耗设备中就会出现性能问题。不过不用担心,有更好的办法将方法附加给构造函数(是不会污染全局环境的哦)。

function fn1(){ var a = 1; function fn(){ a++; console.log(a); } return fn;}// 调用函数var x = fn1();x(); // 2x();//3

Oop面向对象

方法和原型

前面介绍中提到 JavaScript 是一种基于原型的编程语言。在 JavaScript
中,可以把原型当作对象模板一样来使用。原型能避免在实例化对象时创建多余的匿名函数和变量。

澳门新葡萄京官网注册,在 JavaScript
中,prototype 是一个非常特别的属性,可以让我们为对象添加新的属性和方法。现在用原型重写上面的示例看看:

// creating a function

function Person( name, email ) {

    // declaring properties and methods using the (this) keyword

    this.name   = name;
    this.email  = email;

}

// assign a new method to the object’s prototype

Person.prototype.sayHey = function() {

    console.log( "Hey, I’m " + this.name );

}

// instantiating a new object using the constructor function

var steve = new Person( "Steve", "steve@hotmail.com" );

// accessing methods and properties

steve.sayHey();

这个示例中,不再为每个 Person 实例定义 sayHey 方法,而是通过原型模板在各实例中共享这个方法。

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

Instantiation:实例化

当一个对象实例需要从类中创建出来时,类的构造函数就会运行来创建这个实例。这种创建对象实例的过程我们称之为实例化-实例对象被类实例化

 

 

 

Inherited:继承(子类继承父类):

 

 

 

从子类实例化:

 

 

 

 

 

继承性

通过原型链,原型可以用来实例继承。JavaScript
的每一个对象都有原型,而原型是另外一个对象,也有它自己的原型,周而复始…直到某个原型对象的原型是 null——原型链到此为止。

在访问一个方法或属性的时候,JavaScript
首先检查它们是否在对象中定义,如果不,则检查是否定义在原型中。如果在原型中也没找到,则会延着原型链一直找下去,直到找到,或者到达原型链的终端。

现在来看看代码是怎么实现的。可以从上一个示例中的 Person 对象开始,另外再创建一个叫 Employee 的对象。

// Our person object

function Person( name, email ) {

    this.name   = name;
    this.email  = email;

}

Person.prototype.sayHey = function() {

    console.log( "Hey, I’m " + this.name );

}

// A new employee object

function Employee( jobTitle ) {

    this.jobTitle = jobTitle;

}

现在 Employee 只有一个属性。不过既然员工也属于人,我们希望它能从 Person 继承其它属性。要达到这个目的,我们可以在 Employee 对象中调用 Person 的构造函数,并配置原型链。

// Our person object

function Person( name, email ) {

    this.name   = name;
    this.email  = email;

}

Person.prototype.sayHey = function() {

    console.log( "Hey, I’m " + this.name );

}

// A new employee object

function Employee( name, email, jobTitle ) {

    // The call function is calling the Constructor of Person
    // and decorates Employee with the same properties

    Person.call( this, name, email );

    this.jobTitle = jobTitle;

}

// To set up the prototype chain, we create a new object using 
// the Person prototype and assign it to the Employee prototype

Employee.prototype = Object.create( Person.prototype );

// Now we can access Person properties and methods through the
// Employee object

var matthew = new Employee( "Matthew", "matthew@hotmail.com", "Developer" );

matthew.sayHey();

要适应原型继承还需要一些时间,但是这一个必须熟悉的重要概念。虽然原型继承模型常常被认为是
JavaScript
的弱点,但实际上它比传统模型更强大。比如说,在掌握了原型模型之后创建传统模型简直就太容易了。

ECMAScript 6
引入了一组新的关键字用于实现 类。虽然新的设计看起来与传统基于类的开发语言非常接近,但它们并不相同。JavaScript
仍然基于原型。

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"]

构建函数和对象实例

有些人认为 JavaScript
不是真正的面向对象的语言,比如它没有像许多面向对象的语言一样有用于创建class类的声明。JavaScript
用一种称为构建函数的特殊函数来定义对象和它们的特征。构建函数非常有用,因为很多情况下你不知道实际需要多少个对象(实例)。构建函数提供了创建你所需对象(实例)的有效方法,将对象的数据和特征函数按需联结至相应对象。

不像“经典”的面向对象的语言,从构建函数创建的新实例的特征并非全盘复制,而是通过一个叫做原形链的参考链链接过去的。(see Object
prototypes),所以这并非真正的实例,严格的讲,
Javascript 在对象间使用和其它语言不同的机制共享特征。

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

创建对象:

1.工厂模式:调用一个创建对象的函数

函数返回值是那个对象

可以传入参数

冗长

function createNewPerson(name) {

  var obj = {};

  obj.name = name;

  obj.greeting = function () {

    alert(‘Hi! I’m ‘ + this.name + ‘.’);

  }

  return obj;}

var salva = createNewPerson(‘salva’);

salva.name;

salva.greeting();

2.构造函数Constructor(js中的类)

一个构建函数通常是大写字母开头,这样便于区分构建函数和普通函数。

使用this关键字

创建对象是要用new

可创建很多对象,对象有不同命名空间

更高效:实际上函数也是在类中定义的,而不是在对象实例中

原生构造函数:Object(),Array()等,自动存在于执行环境中

 

function Person(name) {

  this.name = name;

  this.greeting = function() {

    alert(‘Hi! I’m ‘ + this.name + ‘.’);

  };}

var person1 = new Person(‘Bob’);

var person2 = new Person(‘Sarah’);

你现在看到页面上有两个对象,每一个保存在不同的命名空间里,当你访问它们的属性和方法时,你需要使用 person1或者
person2
来调用它们。尽管它们有着相同的 name 属性和 greeting() method 它们是各自独立的,所以相互的功能不会冲突。注意它们使用的是自己的 name
值,这也是使用 this
关键字
的原因,它们使用的从实参传入形参的自己的值,而不是其它的什么值。

两个新的对象被创建:

{

  name : ‘Bob’,

  greeting : function() {

    alert(‘Hi! I’m ‘ + this.name + ‘.’);

  }}

{

  name : ‘Sarah’,

  greeting : function() {

    alert(‘Hi! I’m ‘ + this.name + ‘.’);

  }}

New之后经历四个步骤:

1创建对象

2将构造函数的作用域给对象(因此this指向这个对象)

3执行构造函数中的代码

4返回新对象

 

闭包的概念:

3.用new Object()用字面量创建

var person1 = new Object();

person1.name = ‘Chris’;

person1[‘age’] = 38;

person1.greeting = function() {

  alert(‘Hi! I’m ‘ + this.name + ‘.’);}

或者:

var person1 = new Object({

  name : ‘Chris’,

  age : 38,

  greeting : function() {

    alert(‘Hi! I’m ‘ + this.name + ‘.’);

  }});

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

4.调用Object()的create()方法

优点:基于现有的对象(person1)创建新对象(克隆)

缺点:浏览器兼容

var person2 = Object.create(person1);

person2.name

person2.greeting()

立即执行函数(IIFE)

5.最常见的定义对象的方法:

原型  +  构造函数

原型用来放方法,构造函数用来放属性

因为

原型适合动态的添加方法,更灵活,省的在构造函数里面放那么多方法

原型不适合添加属性(全局变量不方便访问this)

 

 

 

 

 

 

 

 

 

 

 

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

对象原型

1.基于原型的语言

JavaScript 常被描述为一种基于原型的语言 (prototype-based
language)
——每个对象拥有一个原型对象,对象以其原型为模板、从原型继承方法和属性。原型对象也可能拥有原型,并从中继承方法和属性,一层一层、以此类推。这种关系常被称为原型链 (prototype chain),它解释了为何一个对象会拥有定义在其他对象中的属性和方法。

准确地说,这些属性和方法定义在 Object 的构造器函数之上,而非对象实例本身。

在传统的 OOP
中,首先定义“类”,此后创建对象实例时,类中定义的所有属性和方法都被复制到实例中。在
JavaScript 中并不如此复制——而是在对象实例和它的构造器之间建立一个连接(作为原型链中的一节),以后通过上溯原型链,在构造器中找到这些属性和方法:

 

上溯原型链:

例子:定义一个构造器函数:

function Person(first, last, age, gender, interests) {

  // 属性与方法定义

  };

然后创建一个对象实例:

var person1
= new Person(‘Bob’, ‘Smith’, 32, ‘male’, [‘music’, ‘skiing’]);

 

调用 person1 的 “实际定义在 Object 上的” 方法时,会发生什么?比如:

person1.valueOf()

1.浏览器首先检查,person1 对象是否具有可用的 valueOf() 方法。

2.如果没有,则浏览器检查 person1
对象的原型对象(即 Person)是否具有可用的 valueof() 方法。

3.如果也没有,则浏览器检查
Person() 构造器的原型对象(即
Object)是否具有可用的
valueOf() 方法。Object 具有这个方法,于是该方法被调用,

 

即:

方法和属性没有被复制到原型链中的其他对象——它们只是通过前述的“上溯原型链”的方式访问。

 

 

注意:没有正式的方法用于直接访问一个对象的原型对象——原型链中的“连接”被定义在一个内部属性中,在 JavaScript 语言标准中用 [[prototype]]
表示(参见 ECMAScript)。然而,大多数现代浏览器还是提供了一个名为
__proto__ (前后各有2个下划线)的属性,其包含了对象的原型。

你可以尝试输入
person1.__proto__ 和 person1.__proto__.__proto__,看看代码中的原型链是什么样的!

 

在截图中能看到person1的原型是一个对象,包含一个构造器函数Person,包含一个原型属性__proto__,属性值是Object,说明在原型链中Person的上一级原型是Object。

 

 

 

 

 

 

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

2.构造函数(类)的prototype 属性:一个对象,在这个对象中定义所有能被继承的成员

 

 

在 JavaScript 控制台输入
“person1.”,你会看到,浏览器将根据这个对象的可用的成员名称进行自动补全。

但实际上,Object有更多属性和方法,不止上面那些,为什么上面没有显示完呢??????????

因为

要被继承的属性和方法是定义在
prototype 属性之上的(你可以称之为子命名空间
(sub namespace)
),

——那些以 Object.prototype. 开头的属性,而非仅仅以 Object. 开头的属性,才会被继承。

prototype 属性的值是一个对象,存储着我们希望被原型链下游的对象继承的属性和方法。

 

 

Object.prototype.watch()、Object.prototype.valueOf() 等等成员(可以在控制台数Object.prototype回车查到所有可被继承的Object的成员),适用于任何继承自 Object() 的对象类型,包括使用构造器创建的新的对象实例。

Object.is()、Object.keys(),以及其他不在 prototype 对象内的成员,不会被“对象实例”或“继承自
Object() 的对象类型”所继承。这些方法/属性仅能被 Object() 构造器自身使用。

 

 

 

 

 

 

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

3.create方法

 

function Person(first, last, age, gender, interests) {

  

  // 属性与方法定义

  

};

var person1=new Person(1,1,1,1,1,);

person2=Object.create(person1);

person2.__proto__;

 

按照MDN上说的,会返回:person1对象

但实际上,

chrome浏览器返回了Person对象

而firefox浏览器返回了Object对象…………………………………………??此处有疑问

                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            

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

4.constructor属性

每个对象实例都有constructor属性,指向对象的构造器(构造函数)

控制台输:

function Person(first, last, age, gender, interests) {

  // 属性与方法定义

};

var person1=new Person(1,1,1,1,1,);

person2=Object.create(person1);

person1.constructor

person1.constructor

都会返回Person构造函数:

function Person(first, last, age, gender, interests) {

  // 属性与方法定义

}

 

**用一个对象的构造器属性创建另一个对象:

var person3
= new person1.constructor(‘Karen’, ‘Stephenson’, 26, ‘female’, [‘playing
drums’, ‘mountain climbing’]);

(因为构造器本质就是构造函数,可以通过括号调用,但和普通函数不同的是,要加new关键字)

 

**constructor还有其他的属性和方法,如获得某对象构造器的name,length(参数个数),constructor(哈哈constructor的constructor,返回Function构造器,因为构造器本身就是函数来的),

 

 

 

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

3.修改prototype属性,(动态更新原型链)

编写Person构造函数/构造器      

function Person(first, last, age, gender, interests) {

        this.name = {

          first,

          last

        };

        this.age = age;

        this.gender = gender;

        this.interests = interests;

        this.bio = function() {}

        this.greeting = function() {

          alert(‘Hi! I’m ‘ + this.name.first + ‘.’);

        };

      };

//为构造器的prototype属性添加新的一个方法:

Person.prototype.farewell = function() {

  alert(this.name.first + ‘ has left the building. Bye for now!’);

}

//控制台输

person1.farewell();

 

你会看到一条警告信息,其中还显示了构造器中定义的人名;这很有用。但更关键的是,整条继承链动态地更新了,任何由此构造器创建的对象实例都自动获得了这个方法。

 

这证明了先前描述的原型链模型。这种继承模型下,上游对象的方法不会复制到下游的对象实例中;下游对象本身虽然没有定义这些方法,但浏览器会通过上溯原型链、从上游对象中找到它们。这种继承模型提供了一个强大而可扩展的功能系统。

 

 

 

注意,给prototype增加属性一般是增加方法

或增加常属性(无法改变的属性constant property)

 

因为

Person.prototype.fullName = this.name.first + ‘ ‘ + this.name.last;

这样加会出错,,,this指代的是当前执行环境的全局范围

 

而加方法

Person.prototype.farewell = function() {

  alert(this.name.first + ‘ has left the building. Bye for now!’);}

这样就会成功,因为此时语句位于函数范围内,从而能够成功地转换为对象实例范围。(是对象调用这个方法)

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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

继承

之前说的prototype继承,是指对象实例能够访问:构造器中prototype属性中的方法。

这里说的继承是 构造器 到 构造器 的继承

 

1.

//Person构造器:

function Person(first, last, age, gender, interests) {

  this.name = {

    first,

    last

  };

  this.age = age;

  this.gender = gender;

  this.interests = interests;

};

//Teacher构造器:

function Teacher(first, last, age, gender, interests, subject) {

  Person.call(this, first, last, age, gender, interests);

  this.subject = subject;

}

Teacher构造器从Person构造器继承了所有属性,又新增了一个subject属性。

这样很**高效**

 

 

 

 

call()函数。基本上,这个函数允许你调用一个在这个文件里 别处定义的函数。第一个参数指明了在你运行这个函数时想对“this”指定的值,也就是说,你可以重新指定你调用的函数里所有“this”指向的对象。其他的变量指明了所有目标函数运行时接受的参数。

(调用 同文件 别的函数 ,能改变/指定 this指向的值 )

 

 

 

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

2.从Person()继承prototype

上面的使用call的方法,让Teacher()继承到了Person()的所有属性,但是此时Teacher()只有一个空的prototype属性(本质也是一个对象),如何让Teacher()从Person()的原型属性里面继承方法呢?

Teacher.prototype = Object.create(Person.prototype);

哈!用老朋友**create**

用create()函数来将Person.prototype这个对象克隆给Teacher.prototype。

因为prototype属性本质也是一个对象,所以可以用create的方法去克隆一个对象给Person.prototype。

 

 

 

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

3.设置Teacher()的constructor

直接从Person()继承,会使Teacher()的prototype属性中的constructor属性指向Person(),也就是说由Teacher()创建的对象实例,查这个对象的构造器时,返回的是Person(), 这是由我们生成Teacher()的方式导致的。(这篇 Stack Overflow post  https://stackoverflow.com/questions/8453887/why-is-it-necessary-to-set-the-prototype-constructor  文章会告诉你详细的原理)

 

 

这或许会成为很大的问题,所以我们需要将其正确设置——你可以回到源代码,在底下加上这一行代码来解决:

 

Teacher.prototype.constructor = Teacher;

 

例如:

(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;i10;i++){} // 循环体内声明的计数变量i也是一个全局console.log(i); // 10for(let j = 0;i10;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 examplexmlHttpRequestObject.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里的几句话,供大家参考:

他们是固定的值,不是变量,让你从“字面上”理解脚本。
(Literals)字符串字面量是由双引号(”)或单引号(’)包围起来的零个或多个字符组成的。(Strings
Literals)对象字面量是由大括号({})括起来的零个或多个对象的属性名-值对。(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中对象的创建有很多种方式,如下:

第一种方式是通过构造函数的形式创建成一个对象

第二种形式是通过{}这种写法直接来创建一个对象,还有一种是通过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); // 11obj.changeVal = 20;console.log(obj.value); // 20

应用:变量监听

例如:html代码如下

!DOCTYPE htmlhtml 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/headbody button onclick="test()"点击/button p 0/p/bodyscript src="test.js" charset="utf-8"/scriptscript 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 objectsalert('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 trueconsole.log(helloFunction === Person.prototype.sayHello); // logs truehelloFunction.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" 属性指向StudentStudent.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 correctlyconsole.log(student1 instanceof Person); // trueconsole.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);

发表评论

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