JS面向对象编程

学过C++等语言的话,你一定明白面向对象的两个基本概念:

  • 类:类是对象的类型模板,例如,定义Student类来表示学生,类本身是一种类型,Student表示学生类型,但不表示任何具体的某个学生;
  • 实例:实例是根据类创建的对象,例如,根据Student类可以创建出xiaoming、xiaohong等多个实例,每个实例表示一个具体的学生,他们全都属于Student类型。

所以,类和实例是大多数面向对象编程语言的基本概念。

BUT,JavaScript不区分类和实例的概念,而是通过原型(prototype)来实现面向对象编程。

创建对象

JavaScript的所有数据都可以看成对象(Object)。
三种创建对象的方法:

  1. 对象字面量
 var xiaoming = {
    name: '小明',
    hello: function () {
        console.log('Hello, ' + this.name + '!');
    }
};
  1. 构造函数
function Student(name) {
    this.name = name;
    this.hello = function() {
         console.log('Hello, ' + this.name + '!');
    }
}
var xiaoming = new Student("小明")
  1. Object.create()构造
    Object.create(proto [, propertiesObject ]) 是E5中提出的一种新的对象创建方式,第一个参数是要继承的原型,如果不是一个子函数,可以传一个null,第二个参数是对象的属性描述符,这个参数是可选的。
var Student = {
    name: 'Robot',
    hello: function () {
        console.log('Hello, ' + this.name + '!');
    }
};
var xiaoming = Object.create(Student);

其中,第二种构造函数,如果不写new,Student就是一个普通函数(也可以说是方法Function),它返回undefined。但是,如果写了new,它就变成了一个构造函数,它绑定的this指向新创建的对象,并默认返回this,也就是说,不需要在最后写return this;了。

\\proto\\属性与prototype属性

我们只看前两种方法,第一种是直接创建对象,第二种是通过方法(Function)来创建对象。
JavaScript对每个创建的对象都会设置一个__proto__属性,指向该对象的构造函数的原型对象。而如果是通过第二种构造函数的方法来创建的创建的对象还会多出一个prototype属性。
解释如下:

//构造函数
function Student(name) {
    this.name = name;
    this.hello = function() {
         console.log('Hello, ' + this.name + '!');
    }
}
var xiaoming = new Student("小明")
var xiaosha = new Student("小傻")

//对象字面量
var Student1 = {
 name: 'XXX',
 hello: function () {
     console.log('Hello, ' + this.name + '!');
 }
};
var xiaohong = {
    name: "xiaohong"
}
xiaohong.__proto__ = Student1;

在创建xiaohong的时候我们直接把xiaohong.\proto\ = Student1;为甚么这样写呢,因为它不是Function创建的对象,当然也就不具备prototype属性哈哈。但是上述代码仅用于演示目的。在编写JavaScript代码时,不要直接用obj.__proto__去改变一个对象的原型,并且,低版本的IE也无法使用__proto__

对象的原型链
对象的原型链

img3
img3

Img1
Img1

说明几点:

  1. 由Img1可以看出,通过构造函数创建的对象除了__proto__属性还有prototype属性。事实上,只要是Function就会有该属性,也即普通函数也有prototype属性,原因是方法(Function)这个对象搞特殊,他还有自己特有的属性——原型属性(prototype),这个属性是一个指针,指向一个对象(这个被指向的对象我们后面细说)。
    2.在JS中,方法(Function)是对象,方法的原型(Function.prototype)也是对象啊

通过原型(prototype)来实现面向对象编程

function Foo(){
  y = 2;
}
typeof Foo.prototype; //"object"
var obj = new Foo();

上面这个函数Foo是作为一个构造器来使用,this指向一个对象,而这个对象的原型会指向构造器的prototype属性(Foo.prototype也是一个对象)

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

正常直接调用Person函数的话,this指向全局对象(在浏览器中为window),不写return的话,返回undefined。如果用new调用的话。this会指向为一个原型为Person.prototype的空对象,通过this.name给这个空对象赋值,绑定的this指向这个新创建的对象(此时已经不空了),并默认返回this,也就是说,不需要在最后写return this了。

call、apply

call方法:
语法:call([thisObj[,arg1[, arg2[, [,.argN]]]]])
定义:调用一个对象的一个方法,以另一个对象替换当前对象。
说明: call 方法可以用来代替另一个对象调用一个方法。call 方法可将一个函数的对象上下文从初始的上下文改变为由 thisObj 指定的新对象。 如果没有提供 thisObj 参数,那么 Global 对象被用作 thisObj。
apply方法:
语法:apply([thisObj[,argArray]])

都知道这两个函数能改变函数执行的上下文,可是我总是搞不懂第一个参数到底传入的是什么。如果是this,那this又是什么。。。
所以今天测试一下,应该是懂了。

function Person(name, age){
    this.name = name;
    this.age = age;
}
function Stu(name, age, className){
    console.log(this) //Stu{}

    Person.call(this,name,age)
    console.log(this) //Stu{name: "Beatrice", age: 12}

    this.className = className;
    console.log(this) /Stu {name: "Beatrice", age: 12, className: 6}
}
var stu1 = new Stu("Beatrice", 12, 6);

最开始传入的是一个原型为Stu.prototype的空Stu{}对象。最后,我们把这个对象赋值给stu1。如何让Stu的实例能继承Person.prototype上的一些方法呢?

Stu.prototype = Object.create(Person.prototype);
Stu.prototype.constructor = Stu;

Object.create()创建一个空对象,并且对象的原型指向它的参数。这里就创建了一个原型指向Person.prototype的空对象。因为Stu.prototype会作为所有new Stu()实例的原型,为了继承Person.prototype上的一些方法,我们就把这个创建出来的空对象赋值给Stu.prototype,这样实例继承Stu.prototype,也就相当于实例继承这个原型为Person.prototype的空对象了。
那你也许会问,为什么不直接赋值Stu.prototype = Person.prototype,而是要通过Object.create呢?
如果直接赋值,Stu有一些自己的方法,当我们想增加Stu自己的方法的时候,也会加在Person上面,这显然不是我们想要的。所以我们搞一个中间的桥梁,也就是这个空对象。
这时,这个创建出的空对象的prototype == Person.prototype == Student.prototype。每一个prototype属性下面自带一个constructor属性,这个属性是指向自身的(所以这是个无限循环了呵呵)。所以prototype属性里面的constructor当然也都一致的,都是Person了。为了保证一致性,所以我们这里把Stu的constructor重新设置为指向自身。

内置构造器的prototype属性

字面量创建的对象var object= {};的原型是Object.prototype
ES5中使用defineProperty()函数来对原型添加属性,而不是直接Object.prototype.x = 1这种形式来添加。
obj.hasOwnProperty()方法,只查找对象obj的方法,而不继续向上查找。

instanceof

[1,2] instanceof Array === true
new Object() instanceof Array === false

在instanceof左侧是对象,右侧是构造器(函数),instanceof用于判断右边这个构造器的prototype属性是否在左侧这个对象的原型链上。

实现继承的方式

    function Person(){

    }
    function Student(){

    }
    Student.prototype = Person.prototype; //1 这是错的!!
    Student.prototype = new Person(); //2  有点小问题,当构造函数要传入参数的时候
    Student.prototype = Object.create(Person.prototype);  //3 这个方法较好,但要ES5
    Student.prototype.constructor = Student;
    //对于1,2,3步骤有一个比较好的方法,自己封装一个Object.create()方法
    if(!Object.create){  //4.
        Object.create = function(proto){
            function F(){}
            F.prototype = proto;
            return new F;
        }
    }
  1. 错的,因为子类有自己的方法,修改Student不能同时也把Person改掉
  2. 调用构造函数方法,也能将Student。prototype指向一个原型为Person.prototype的对象,但是这个方法当构造函数要传入参数的时候会有问题,因为我们还没有实例化呢,你参数要传入什么呢?对吧~
  3. 较为理想,Student的原型是个空的对象,这样修改就不会改到Person,But ES5采用。所以我们自己封装了一个4.
  4. 在ES5之前,模拟一个Object.create(),原理是模拟一个空函数,并且把这个空函数的prototype属性赋值为proto这个我们想作为原型的对象,用return new F这样呢就创建出一个空对象,这个对象的原型指向proto这个参数。

题目描述:
请给Array本地对象增加一个原型方法,它用于删除数组条目中重复的条目(可能有多个),返回值是一个包含被删除的重复条目的新数组。

Array.prototype.distinct = function() {
    var ret = [];
    for (var i = 0; i < this.length; i++)
    {
        console.log(this);
        for (var j = i+1; j < this.length;) {   
            if (this[i] === this[j]) {              
                var tmp = this.splice(j, 1);
                //console.log(tmp)
                ret.push(tmp[0]);
            } else {
                j++;
            }
        }
     }
     return ret;
}
//for test
console.info(['a','b','c','d','b','a','e'].distinct())
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 156,069评论 4 358
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,212评论 1 287
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 105,912评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,424评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,741评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,194评论 1 206
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,553评论 2 307
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,289评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,923评论 1 237
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,251评论 2 240
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,775评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,144评论 2 249
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,698评论 3 228
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,936评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,658评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,214评论 2 267
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,159评论 2 258

推荐阅读更多精彩内容