博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
JavaScript学习总结(四)——this、原型链、javascript面向对象
阅读量:5923 次
发布时间:2019-06-19

本文共 17110 字,大约阅读时间需要 57 分钟。

一、this

在JavaScript中this表示:谁调用它,this就是谁

JavaScript是由对象组成的,一切皆为对象,万物皆为对象。this是一个动态的对象,根据调用的对象不同而发生变化,当然也可以使用call、apply修改this指向的对象。它代表函数运行时,自动生成的一个内部对象,只能在函数内部使用

1.0、猜猜答案

代码如下:

            
JavaScript this

JavaScript this

请问输出到控制台的结果是什么?相同吗?输出两个什么对象?

答案:

Window
View Code

1.1、JavaScript中函数与方法的区分

在面向过程的语言中我们习惯把完成某个特定功能的代码块称为“函数”或“过程”,当然过程一般没有返回值。在面向对象语言中我们把对象的功能称为“方法”。但JavaScript是种介于面向对象与面向过程中间的语言,同样的方法有时是函数,有时是方法,如下所示:

可以简单的认为如果调用时没有通过对象没有指定上下文则为函数,否则为方法。

1.2、指向全局对象

当在全部范围内使用 this,它将会指向全局对象。一般是window对象,但全局对象不一定只有window,特别是在node.js环境中。作为函数调用时一般指向全局对象。

结果:

运行结果:

1.3、作为方法调用

当函数作为方法调用时this指向调用该方法的对象。

function show()            {                //当obj1.view()时this就是obj1,obj2.view()时this就是obj2                console.log(this.name);            }                        var obj1={name:"jack",view:show};            obj1.view();                        var obj2={name:"mark",view:show};            obj2.view();

运行结果:

示例代码:

运行结果:

示例代码:

运行结果:

1.4、在构造函数中的this

构造函数中的this指向新创建的对象,new出谁this就是谁。

示例代码:

运行结果:

按照严格的语法,构造函数不应该返回值,但是JavaScript是允许构造方法返回值的,默认返回this,修改后的示例如下:

this.name="吉娃娃";            function Dog(name)            {                this.name=name;                this.bark=function(){                    console.log(this.name+"在叫,汪汪...");                }                return this.bark;            }                        var dog1=new Dog("哈士奇");            dog1();

运行结果:

 

1.5、指定this指向的对象

当调用方法是通过Function.call,或Function.apply时this为调用时指定的对象,如果没有指定则为顶层对象,浏览器中是window。

示例代码:

this.name="伽啡";            function Cat(name)            {                this.name=name;            }            var bark=function(n){                console.log(this.name+"叫了"+(n||1)+"声喵喵...");            }                        var cat1=new Cat("波斯");            var cat2=new Cat("龙猫");                        bark.call(cat1,5);  //调用时this是cat1            bark.apply(cat2,[8]);  //调用时this是cat2            bark.apply(window,[9]);  //调用时this是window            bark.apply();  //调用时this是顶层对象

 

运行结果:

1.6、作为事件时的this

如果页面中的元素与事件进行绑定时事件中的this一般指向网页元素,如按钮,文本框等。但是在元素中直接指定要执行的函数则this指向window顶层对象。

            
this

当点击A-C时的效果:

当点击D时的效果:

 

当点击按钮E时的效果:

            
this

 加载时运行结果:

点击按钮F时的效果:

点击按钮G时的效果:

 

在HTML元素上直接指定事件严格来说都不能说是事件绑定,只能描述是当按钮点击完成后执行的函数。如果想将执行时的对象带回,可以增加参数this。

1.7、小结

函数调用可以如下几种基本形式:

1)、fun(x,y);

2)、obj.fun(x,y);

3)、fun.apply(obj,[x,y]);

4)、fun.call(obj,x,y);

第1,2种调用的方式最终将转换成3,4方法,也就是说1,2只是一种简化的语法糖,那么this就是apply与obj的第1个参数,指向调用时的上下文。

二、原型(prototype)与原型链

在javascript面向对象中关于原型(prototype)、原型链、__proto__、Function、Object等内容是较难理解的,先上几张便于大家理解的图:

JavaScript藏宝图

JavaScript Object Layout:

Object Relationship Diagram with Inheritance

JavaScript是一种通过原型实现继承的语言与别的高级语言是有区别的,像java,C#是通过类型决定继承关系的,JavaScript是的动态的弱类型语言,总之可以认为JavaScript中所有都是对象,在JavaScript中,原型也是一个对象,通过原型可以实现对象的属性继承JavaScript的对象中都包含了一个" prototype"内部属性,这个属性所对应的就是该对象的原型

"prototype"作为对象的内部属性,是不能被直接访问的。所以为了方便查看一个对象的原型,Firefox和Chrome内核的JavaScript引擎中提供了"__proto__"这个非标准的访问器(ECMA新标准中引入了标准对象原型访问器"Object.getPrototype(object)")。

1.1、为什么需要prototype

现在有一个Student构造函数,通过new调用该构造函数可以创建一个新的对象,示例如下:

//构造方法,用于创建学生对象            function Student(name) {                this.name = name;            }                        var tom=new Student("tom");            tom.show=function(){                console.log(this.name);            }                        var rose=new Student("rose");            rose.show=function(){                console.log(this.name);            }                                    tom.show();            rose.show();

运行结果:

上面的示例中tom与rose都需要show方法,创建对象后我们直接为两个对象分别都增加了show方法,但是这样做的弊端是如果再增加更多对象也需要添加show方法,最好的办法是修改构造方法Student,如下所示:

//构造方法,用于创建学生对象            function Student(name) {                this.name = name;                this.show = function() {                    console.log(this.name);                }            }            var tom = new Student("tom");            var rose = new Student("rose");            tom.show();            rose.show();

但是如果Student构造函数是一个内置的函数或是其它框架定义的类型,则修改就比较麻烦了,可以不修改源码的情况扩展该对象的原型也可以达到目的,如下所示:

//构造方法,用于创建学生对象            function Student(name) {                this.name = name;            }                        //修改Student对象的原型,增加show方法            Student.prototype.show = function() {                console.log(this.name);            }                        var tom = new Student("tom");            var rose = new Student("rose");            tom.show();            rose.show();

在示例中我们并没有修改Student这个构造函数而是修改了了Student的原型对象,类似它的父类,如下图所示:

此时你也许会认为所有的Object都增加了show方法,这样想是错误的,因为Student的原型是一个对象而不是像其它高级语中的类型,我们修改的只是Student的原型对象,不会影响其它的对象。

var mark=new Object();            mark.name="mark";                        var lucy={name:"lucy"};            //mark.show();            //lucy.show();

此处的两个show方法是错误的。总之原型的主要作用就是为了实现继承与扩展对象。

在JavaScript中,原型也是一个对象,通过原型可以实现对象的属性继承,JavaScript的对象中都包含了一个”[[Prototype]]”内部属性,这个属性所对应的就是该对象的原型。

“[[Prototype]]”作为对象的内部属性,是不能被直接访问的。所以为了方便查看一个对象的原型,Firefox和Chrome中提供了__proto__这个非标准(不是所有浏览器都支持)的访问器(ECMA引入了标准对象原型访问器”Object.getPrototype(object)”)。在JavaScript的原型对象中,还包含一个”constructor”属性,这个属性对应创建所有指向该原型的实例的构造函数

 

1.2、typeof与instanceof

1.2.1、typeof

在 JavaScript 中,判断一个变量的类型可以用typeof,如:

var str1="Hello";             var str2=new String("Hello");                         console.log(typeof 1);            console.log(typeof(true));            console.log(typeof str1);            console.log(typeof str2);            console.log(typeof([1,2,3]));            console.log(typeof Date);            console.log(typeof undefined);            console.log(typeof null);            console.log(typeof zhangguo);

运行结果:

1)、数字类型, typeof 返回的值是 number。比如说:typeof(1),返回值是number

2)、字符串类型, typeof 返回的值是 string。比如typeof("123")返回值是string。
3)、布尔类型, typeof 返回的值是 boolean 。比如typeof(true)返回值是boolean。
4)、对象、数组、null 返回的值是 object 。比如typeof(window),typeof(document),typeof(null)返回的值都是object。
5)、函数类型,返回的值是 function。比如:typeof(eval),typeof(Date)返回的值都是function。
6)、不存在的变量、函数或者undefined,将返回undefined。比如:typeof(abc)、typeof(undefined)都返回undefined。

1.2.2、instanceof

使用 typeof 运算符时采用引用类型存储值会出现一个问题,无论引用的是什么类型的对象,它都返回 "object"。ECMAScript 引入了另一个 Java 运算符 instanceof 来解决这个问题。instanceof 运算符与 typeof 运算符相似,用于识别正在处理的对象的类型。与 typeof 方法不同的是,instanceof 方法要求开发者明确地确认对象为某特定类型。

instanceof用于判断某个对象是否被另一个函数构造。

例如:

var oStringObject = new String("hello world");
console.log(oStringObject instanceof String); // 输出 "true"

var str1="Hello";             var str2=new String("Hello");                         console.log(str1 instanceof String);   //string false            console.log(str2 instanceof String);  //object true                        console.log(1 instanceof Number); //false                        function Foo(){};                        var f1=new Foo();            console.log(f1 instanceof Foo);

运行结果:

1.3、Function与Object

1.3.1、Function

函数就是对象,代表函数的对象就是函数对象。所有的函数对象是被Function这个函数对象构造出来的。Function是最顶层的构造器。它构造了系统中所有的对象,包括用户自定义对象,系统内置对象,甚至包括它自已。这也表明Function具有自举性(自已构造自己的能力)。这也间接决定了Function的call和constructor逻辑相同。每个对象都有一个 constructor 属性,用于指向创建其的函数对象。

先来看一个示例:

function Foo(a, b, c) {  //Foo这个构造函数由Function构造,函数也是对象                return a * b * c;            }                        var f1=new Foo();   //f1由Foo构造,Foo是一个构造函数,可以理解为类                        console.log(Foo.length);   //参数个数 3            console.log(typeof Foo.constructor);  //function            console.log(typeof Foo.call);  //function            console.log(typeof Foo.apply);  //function            console.log(typeof Foo.prototype);  //object object

运行结果:

 

函数与对象具有相同的语言地位

没有类,只有对象
函数也是一种对象,所谓的函数对象
对象是按引用来传递的

function Foo() {};            var foo = new Foo();            //Foo为foo的构造函数,简单理解为类型            console.log(foo instanceof Foo); // true                        //但是Function并不是foo的构造函数            console.log(foo instanceof Function); // false                        //Function为Foo的构造函数            alert(Foo instanceof Function); //true

上面的代码解释了foo和其构造函数Foo和Foo的构造函数Function的关系,Foo是由Function构造得到,可以简单理解为,系统中有一个这样的构造函数:

function Function(name,body)            {            }                        var Foo=new Function("Foo","");

如上面例子中的构造函数,JavaScript中函数也是对象,所以就可以通过_proto_查找到构造函数对象的原型。

Function对象作为一个函数,就会有prototype属性,该属性将对应”function () {}”对象。

Function对象作为一个对象,就有__proto__属性,该属性对应”Function.prototype”,也就是说,”Function._proto_ === Function.prototype”。

在这里对“prototype”和“proto”进行简单的介绍:

对于所有的对象,都有__proto__属性,这个属性对应该对象的原型。

对于函数对象,除了__proto__属性之外,还有prototype属性,当一个函数被用作构造函数来创建实例时,该函数的prototype属性值将被作为原型赋值给所有对象实例(也就是设置实例的__proto__属性)

1.3.2、Object

对于Object它是最顶层的对象,所有的对象都将继承Object的原型,但是你也要明确的知道Object也是一个函数对象,所以说Object是被Function构造出来的。

alert(Function instanceof Function);//true alert(Function instanceof Object);//true alert(Object instanceof Function);//true function Foo() {};var foo = new Foo();alert(foo instanceof Foo); // truealert(foo instanceof Function); // falsealert(foo instanceof Object); // truealert(Foo instanceof Function); // truealert(Foo instanceof Object); // true

JavaScript 原型链

function A() {
this.x="x";}; var a=new A(); function B() {
this.y="y"}; B.prototype=a; var b=new B(); function C() {
this.z="z"}; C.prototype=b; var c=new C(); console.log(c instanceof C); console.log(c instanceof B); console.log(c instanceof A); console.log(c instanceof Object); console.log(c.x+","+c.y+","+c.z);

运行结果:

当查找一个对象的属性时,JavaScript 会向上遍历原型链,直到找到给定名称的属性为止,到查找到达原型链的顶部(也就是 Object.prototype),如果仍然没有找到指定的属性,就会返回 undefined。

Object对象本身是一个函数对象。既然是Object函数,就肯定会有prototype属性,所以可以看到”Object.prototype”的值就是”Object {}”这个原型对象。反过来,当访问”Object.prototype”对象的”constructor”这个属性的时候,就得到了Obejct函数。

另外,当通过”Object.prototype._proto_”获取Object原型的原型的时候,将会得到”null”,也就是说”Object {}”原型对象就是原型链的终点了。

1.4、通过prototype扩展对象

JavaScript内置了很多对象,如Array、Boolean、Date、Function、Number、Object、String 

假设我们想给String类型增加一个repeat方法,实现重复字符,如"a".rpt(),则将输出aa,"a".rpt(5),输出“aaaaa”,因为String是JavaScript中内置的对象,可以通过修改该对象的原型达到目的:

            
prototype原型

运行结果:

示例中给String对象的原型增加了一个rpt方法,所有的String都是衍生自String.prototype,那所有的String对象都将获得rpt方法。

//扩展String类型,增加trim方法用于删除字符串的首尾空格            String.prototype.trim=function()            {                return this.replace(/^\s+|\s+$/igm,'');            }                        console.log("[begin]"+"  Hello JavaScript  ".trim()+"[end]");

运行结果:

为了扩展更加方便,可以修改Function的原型,给每一个函数衍生的对象增加方法method用于扩展。

//修改函数对象的原型,添加method方法,扩展所有的函数            Function.prototype.method=function(name,fun){                //如果当前扩展的函数的原型中不包含名称为name的对象                if(!this.prototype[name]){                    //添加                    this.prototype[name]=fun;                }            }                        String.method("show",function(){                console.log("输出:"+this);            });                        "Hello".show();            "JavaScript".show();

运行结果:

1.5、通过prototype调用函数

我们可以通过对象调用某个方法,因为这个对象的原型中已经定义好了该方法,其实我们通过原型也可以直接调用某个方法,有些方法只存在原型中,只有当前类型关联了该原型才可以获得该方法,但有时我们需要使用该方法去处理非该原型下的对象,如:

function add(x,y,z)            {                var array1=[].slice.call(arguments,0,3);                console.log(array1);                var array2=Array.prototype.slice.apply(arguments,[0,3]);                console.log(array2);            }            add(1,2,8,9,10);

运行结果:

示例代码:

var str1 = "Hello JavaScript";            console.log(str1.toUpperCase()); //传统的调用办法            var str2=String.prototype.toUpperCase.apply(str1); //通过原形直接调用            console.log(str2);                        var str3=String.prototype.toUpperCase.call(str1); //通过原形直接调用            console.log(str3);                        var array1=[2,3,5,7,8,9,10];            var array2=array1.slice(1,3);            console.log(array2);                        var slice=Array.prototype.slice;            console.log(slice.apply(array1,[1,3]));//通过原形直接调用            console.log(slice.call(array1,1,3));

运行结果:

练习:扩展Date,增加一个显示中文日期的方法,如:new Date().trandition(),输出2016年08月09日 23点15分18秒;使用prototype的方式间接调用该方法。

1.6、prototype和__proto__的区别

prototype是函数才有的属性,prototype本身就是一个对象,prototype对象中拥有constractor构造器(该构造器反向指回函数)

__proto__是对象带有属性,用于访问创建对象的类型所对应的原型

字面量创建的对象(o={})或Object创建的对象__proto__指向Object.prototype

示例:

var a = {};console.log(a.prototype);  //undefinedconsole.log(a.__proto__);  //Object {}var b = function(){}console.log(b.prototype);  //b {}console.log(b.__proto__);  //function() {}
            

结果:

1.7、__proto__属性指向

/*1、字面量方式*/var a = {};console.log(a.__proto__);  //Object {}console.log(a.__proto__ === a.constructor.prototype); //true/*2、构造器方式*/var A = function(){};var a = new A();console.log(a.__proto__); //A {}console.log(a.__proto__ === a.constructor.prototype); //true/*3、Object.create()方式*/var a1 = {a:1}var a2 = Object.create(a1);console.log(a2.__proto__); //Object {a: 1}console.log(a.__proto__ === a.constructor.prototype); //false(此处即为图1中的例外情况)

 示例:

            

结果:

1.8、原型链

示例:

var A = function(){};var a = new A();console.log(a.__proto__); //A {}(即构造器function A 的原型对象)console.log(a.__proto__.__proto__); //Object {}(即构造器function Object 的原型对象)console.log(a.__proto__.__proto__.__proto__); //null

示例:

            

结果:

当查找一个对象的属性时,JavaScript 会向上遍历原型链,直到找到给定名称的属性为止,到查找到达原型链的顶部(也就是 Object.prototype),如果仍然没有找到指定的属性,就会返回 undefined。

function Person(name, age){     this.name = name;     this.age = age;   } Person.prototype.MaxNumber = 9999;Person.__proto__.MinNumber = -9999;var will = new Person("Will", 28); console.log(will.MaxNumber); // 9999 console.log(will.MinNumber); // undefined

在这个例子中分别给”Person.prototype “和” Person.proto”这两个原型对象添加了”MaxNumber “和”MinNumber”属性,这里就需要弄清”prototype”和”proto”的区别了。

“Person.prototype “对应的就是Person构造出来所有实例的原型,也就是说”Person.prototype “属于这些实例原型链的一部分,所以当这些实例进行属性查找时候,就会引用到”Person.prototype “中的属性。

对象创建方式影响原型链

var July = {    name: "张三",    age: 28,    getInfo: function(){      console.log(this.name + " is " + this.age + " years old");    } } console.log(July.getInfo());

当使用这种方式创建一个对象的时候,原型链就变成下图了. July对象的原型是”Object.prototype”也就是说对象的构建方式会影响原型链的形式。

1.9、构造对象

在JavaScript中,每个函数 都有一个prototype属性,当一个函数被用作构造函数来创建实例时,这个函数的prototype属性值会被作为原型赋值给所有对象实例(也就是设置 实例的`__proto__`属性),也就是说,所有实例的原型引用的是函数的prototype属性。(只有函数对象才会有这个属性!)

new 的过程分为三步  

var p = new Person('张三',20);1. var p={}; 初始化一个对象p。2. p._proto_=Person.prototype;,将对象p的 __proto__ 属性设置为 Person.prototype3. Person.call(p,”张三”,20);调用构造函数Person来初始化p。

 

1、prototype是函数才有的属性

2、__proto__是每个对象都有的属性,它不是W3C的标准属性,它指向prototype

3、__proto__可以理解为“构造器原型”,__proto__===constructor.prototype(Object.create()创建的对象不适用)

4、当js引擎查找对象的属性时,先查找对象本身是否存在该属性,如果不存在会在原型链上查找,但不会查找自己的prototype

5、函数的原型对象constructor默认指向函数本身,原型对象除了有原型属性外,为了实现继承,还有一个原型链指针__proto__,该指针指向上一层的原型对象,而上一层的原型对象的结构依然类似,这样利用__proto__一直指向Object的原型对象上,而Object的原型对象用Object.prototype.__proto__ = null表示原型链的最顶端,如此变形成了javascript的原型链继承,同时也解释了为什么所有的javascript对象都具有Object的基本方法。

 

三、JavaScript面向对象(OOP)

3.1、封装

因为在JavaScript中没有访问修饰符,也没有块级作用域,this所定义的属性默认对外就是公开访问的,如下示例:

function Animal(){                this.name="动物";                this.weight=0;  //重量            }                        var cat=new Animal();            cat.name="猫";            cat.weight=-90;            console.log(cat);

输出:

weight是-90,不正确,封装:

            

结果:

function Animal()            {                this.name="动物";                this.getName=function(){                    return this.name;                }                this.setName=function(name){                    this.name=name;                }                this.bark=function(){                    console.log(this.name+"在叫...");                }            }            var animal=new Animal();            animal.setName("小狗");            animal.bark();

这里定义的一个构造函数,将name封装成属性,Animal函数本身用于封装动物的属性与行为,运行结果:

3.2、继承

JavaScript的继承的实现主要依靠prototype(原型)来实现,再增加两个动物,如狗与猫:

function Animal()            {                this.name="动物";                this.getName=function(){                    return this.name;                }                this.setName=function(name){                    this.name=name;                }                this.bark=function(){                    console.log(this.name+"在叫...");                }            }                        function Dog(){                this.hunt=function(){console.log("狗在捕猎");};            }            //指定Dog构造函数的原型对象,简单理解为父类            Dog.prototype=new Animal();                        function Cat(){                this.hunt=function(){console.log("猫在抓老鼠");};            }            Cat.prototype=new Animal();                        var dog=new Dog();            dog.bark();            dog.hunt();                        var cat=new Cat();            cat.bark();            cat.hunt();

运行结果:

3.3、多态

java与C#中的多态主要体现在重载与重写上,因为JavaScript是弱类型的,类方法参数是动态指定的所以并没有真正意义上的重载,只能在方法中判断参数达到目的。

            
JavaScript面向对象

运行结果:

 

示例:

            

结果:

 

练习:请使用javascript完成一个简单工厂设计模式。

四、示例下载

五、视频

转载地址:http://oasvx.baihongyu.com/

你可能感兴趣的文章
Windows与Linux双系统安装
查看>>
PHP中stdClass
查看>>
gulp常用插件及其使用(更新2016-05-19)
查看>>
资源分享-开发工具-Java2ObjC
查看>>
把windows下的testlink迁移到linux下
查看>>
HTML文字版面的编辑2
查看>>
fackebook的用户体验分析
查看>>
Perseus-BERT——业内性能极致优化的BERT训练方案
查看>>
【ZooKeeper Notes 16】避免羊群效应(Herd Effect)
查看>>
jquery form 表单转json发送json格式数据
查看>>
33. 拦截过滤器模式
查看>>
C# Winfrom Treeview树形结构使用
查看>>
2016年4月5日:调用转换
查看>>
《FLEX4.0 RIA开发详解》自学笔记 CH04 数据类型
查看>>
关于 Subversion 协议动态代理服务器
查看>>
Error:java: javacTask: source release 1.8 requires target release 1.8
查看>>
SharePoint 2013实例1—构建三层服务器场8—配置WEB层
查看>>
php实践
查看>>
EXCEL打开CSV文件乱码的解决方法
查看>>
ios第六天
查看>>