必威-必威-欢迎您

必威,必威官网企业自成立以来,以策略先行,经营致胜,管理为本的商,业推广理念,一步一个脚印发展成为同类企业中经营范围最广,在行业内颇具影响力的企业。

也许有点难懂,继承讲解

2019-09-22 02:16 来源:未知

JavaScript 深入之new的模拟实现

2017/05/26 · JavaScript · new

原文出处: 冴羽   

JavaScript 深入之bind的模拟实现

2017/05/26 · JavaScript · bind

原文出处: 冴羽   

JavaScript 深入之创建对象的多种方式以及优缺点

2017/05/28 · JavaScript · 对象

原文出处: 冴羽   

JavaScript 深入之继承的多种方式和优缺点

2017/05/28 · JavaScript · 继承

原文出处: 冴羽   

JavaScript创建对象方法总结精彩博文
javascript继承讲解精彩博文
于江水 继承讲解

new

一句话介绍 new:

new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象类型之一

也许有点难懂,我们在模拟 new 之前,先看看 new 实现了哪些功能。

举个例子:

// Otaku 御宅族,简称宅 function Otaku (name, age) { this.name = name; this.age = age; this.habit = 'Games'; } // 因为缺乏锻炼的缘故,身体强度让人担忧 Otaku.prototype.strength = 60; Otaku.prototype.sayYourName = function () { console.log('I am ' + this.name); } var person = new Otaku('Kevin', '18'); console.log(person.name) // Kevin console.log(person.habit) // Games console.log(person.strength) // 60 person.sayYourName(); // I am Kevin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Otaku 御宅族,简称宅
function Otaku (name, age) {
    this.name = name;
    this.age = age;
 
    this.habit = 'Games';
}
 
// 因为缺乏锻炼的缘故,身体强度让人担忧
Otaku.prototype.strength = 60;
 
Otaku.prototype.sayYourName = function () {
    console.log('I am ' + this.name);
}
 
var person = new Otaku('Kevin', '18');
 
console.log(person.name) // Kevin
console.log(person.habit) // Games
console.log(person.strength) // 60
 
person.sayYourName(); // I am Kevin

从这个例子中,我们可以看到,实例 person 可以:

  1. 访问到 Otaku 构造函数里的属性
  2. 访问到 Otaku.prototype 中的属性

接下来,我们可以尝试着模拟一下了。

因为 new 是关键字,所以无法像 bind 函数一样直接覆盖,所以我们写一个函数,命名为 objectFactory,来模拟 new 的效果。用的时候是这样的:

function Otaku () { …… } // 使用 new var person = new Otaku(……); // 使用 objectFactory var person = objectFactory(Otaku, ……)

1
2
3
4
5
6
7
8
function Otaku () {
    ……
}
 
// 使用 new
var person = new Otaku(……);
// 使用 objectFactory
var person = objectFactory(Otaku, ……)

bind

一句话介绍 bind:

bind() 方法会创建一个新函数。当这个新函数被调用时,bind() 的第一个参数将作为它运行时的 this,之后的一序列参数将会在传递的实参前传入作为它的参数。(来自于 MDN )

由此我们可以首先得出 bind 函数的两个特点:

  1. 返回一个函数
  2. 可以传入参数

写在前面

这篇文章讲解创建对象的各种方式,以及优缺点。

但是注意:

这篇文章更像是笔记,因为《JavaScript高级程序设计》写得真是太好了!

写在前面

本文讲解JavaScript各种继承方式和优缺点。

但是注意:

这篇文章更像是笔记,哎,再让我感叹一句:《JavaScript高级程序设计》写得真是太好了!

JavaScript创建对象方式总结

  • object构造函数、对象字面量
//object构造函数
// 优点:简单方便
// 缺点:批量创建对象很麻烦,不能使用instanceof来确定对象类型
var person = new Object();
person.name = "masike";
person.age=19;
person.job="student";
person.sayName=function(){
    console.log(this.name);
};

//字面量
var person = {
    name:"masike",
    age:22,
    job:"student",
    sayName:function(){
        console.log(this.name);
    }
}```
- 工厂模式:简单的函数创建对象,为对象添加属性和方法,然后返回对象,这个模式后来被构造函数所取代。
```JavaScript
//工厂模式
// 优点:减少了代码量
// 缺点:未能解决对象识别问题
function createPerson(name,age,job){
    var o=new Object();
    o.name=name;
    o.age=19;
    o.job="student";
    o.sayName=function(){
        console.log(this.name);
    }
    return o;
}
var person1=createPerson("masike",19,"student");
var person2=createPerson("withershins",20,"worker");```
- 构造函数模式:自定义引用类型,像创建对象实例一样使用new操作符,缺点是每个成员无法得到复用,包括函数。

//构造函数模式
//优点:在工厂模式的基础下解决了对象识别问题
//缺点:每个实例的方法都是独立的,多数情况下同个对象的实例方法都是一样的
function Person(name,age,job){
this.name=name;
this.age=age;
this.job=job;
this.sayName=function(){
console.log(this.name);
}
}
var person1=new Person("masike",19,"student");
var person2=new Person("withershins",19,"worker");
//偏方
function Person(name,age,job){
this.name=name;
this.age=age;
this.job=job;
this.sayName=sayName;
}
function sayName(){
console.log(this.name);
}
var person1=new Person("masike",19,"student");
var person2=new Person("withershins",19,"worker");```

  • 原型模式:使用prototype属性共享属性和方法。
//原型模式
//优点:公用原型减少了赘余
//缺点:在原型的改变会影响到所有的实例,于是实例没有了独立性
function Person(){
}
Person.prototype.name="masike";
Person.prototype.age=19;
Person.prototype.job="student";
Person.prototype.sayName=function(){
    console.log(this.name);
}
var person1=new Person();
person1.sayName();
var person2=new Person();
person2.sayName();
console.log(person1.sayName==person2.sayName);```
- 组合使用构造函数模式和原型模式:构造函数定义实例属性,原型定义共享的属性和方法。

//组合使用构造函数和原型模式
//优点:结合了构造函数和原型模式的优点,并解决了缺点
//缺点:代码没有被很好地封装起来
function Person(name,age,job){
this.name=name;
this.age=age;
this.job=student;
this.friends=["num1","num2"];
}
Person.prototype={
constructor:Person,
sayName:function(){
console.log(this.name);
}
}
var person1=new Person("masike",19,"student");
var person2=new Person("withershins",19,"worker");
person1.friends.push("vash");
console.log(person1.friends);
console.log(person2.friends);
console.log(person1.friends===person2.friends);
console.log(person1.sayName===person2.sayName);```

初步实现

分析:

因为 new 的结果是一个新对象,所以在模拟实现的时候,我们也要建立一个新对象,假设这个对象叫 obj,因为 obj 会具有 Otaku 构造函数里的属性,想想经典继承的例子,我们可以使用 Otaku.apply(obj, arguments)来给 obj 添加新的属性。

在 JavaScript 深入系列第一篇中,我们便讲了原型与原型链,我们知道实例的 __proto__ 属性会指向构造函数的 prototype,也正是因为建立起这样的关系,实例可以访问原型上的属性。

现在,我们可以尝试着写第一版了:

// 第一版代码 function objectFactory() { var obj = new Object(), Constructor = [].shift.call(arguments); obj.__proto__ = Constructor.prototype; Constructor.apply(obj, arguments); return obj; };

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 第一版代码
function objectFactory() {
 
    var obj = new Object(),
 
    Constructor = [].shift.call(arguments);
 
    obj.__proto__ = Constructor.prototype;
 
    Constructor.apply(obj, arguments);
 
    return obj;
 
};

在这一版中,我们:

  1. 用new Object() 的方式新建了一个对象 obj
  2. 取出第一个参数,就是我们要传入的构造函数。此外因为 shift 会修改原数组,所以 arguments 会被去除第一个参数
  3. 将 obj 的原型指向构造函数,这样 obj 就可以访问到构造函数原型中的属性
  4. 使用 apply,改变构造函数 this 的指向到新建的对象,这样 obj 就可以访问到构造函数中的属性
  5. 返回 obj

更多关于:

原型与原型链,可以看《JavaScript深入之从原型到原型链》

apply,可以看《JavaScript深入之call和apply的模拟实现》

经典继承,可以看《JavaScript深入之继承》

复制以下的代码,到浏览器中,我们可以做一下测试:

function Otaku (name, age) { this.name = name; this.age = age; this.habit = 'Games'; } Otaku.prototype.strength = 60; Otaku.prototype.sayYourName = function () { console.log('I am ' + this.name); } function objectFactory() { var obj = new Object(), Constructor = [].shift.call(arguments); obj.__proto__ = Constructor.prototype; Constructor.apply(obj, arguments); return obj; }; var person = objectFactory(Otaku, 'Kevin', '18') console.log(person.name) // Kevin console.log(person.habit) // Games console.log(person.strength) // 60 person.sayYourName(); // I am Kevin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function Otaku (name, age) {
    this.name = name;
    this.age = age;
 
    this.habit = 'Games';
}
 
Otaku.prototype.strength = 60;
 
Otaku.prototype.sayYourName = function () {
    console.log('I am ' + this.name);
}
 
function objectFactory() {
    var obj = new Object(),
    Constructor = [].shift.call(arguments);
    obj.__proto__ = Constructor.prototype;
    Constructor.apply(obj, arguments);
    return obj;
};
 
var person = objectFactory(Otaku, 'Kevin', '18')
 
console.log(person.name) // Kevin
console.log(person.habit) // Games
console.log(person.strength) // 60
 
person.sayYourName(); // I am Kevin

[]~( ̄▽ ̄)~**

返回函数的模拟实现

从第一个特点开始,我们举个例子:

var foo = { value: 1 }; function bar() { console.log(this.value); } // 返回了一个函数 var bindFoo = bar.bind(foo); bindFoo(); // 1

1
2
3
4
5
6
7
8
9
10
11
12
var foo = {
    value: 1
};
 
function bar() {
    console.log(this.value);
}
 
// 返回了一个函数
var bindFoo = bar.bind(foo);
 
bindFoo(); // 1

关于指定 this 的指向,我们可以使用 call 或者 apply 实现,关于 call 和 apply 的模拟实现,可以查看《JavaScript深入之call和apply的模拟实现》。我们来写第一版的代码:

// 第一版 Function.prototype.bind2 = function (context) { var self = this; return function () { self.apply(context); } }

1
2
3
4
5
6
7
8
// 第一版
Function.prototype.bind2 = function (context) {
    var self = this;
    return function () {
        self.apply(context);
    }
 
}

1. 工厂模式

function createPerson(name) { var o = new Object(); o.name = name; o.getName = function () { console.log(this.name); }; return o; } var person1 = createPerson('kevin');

1
2
3
4
5
6
7
8
9
10
11
function createPerson(name) {
    var o = new Object();
    o.name = name;
    o.getName = function () {
        console.log(this.name);
    };
 
    return o;
}
 
var person1 = createPerson('kevin');

缺点:对象无法识别,因为所有的实例都指向一个原型

1.原型链继承

function Parent () { this.name = 'kevin'; } Parent.prototype.getName = function () { console.log(this.name); } function Child () { } Child.prototype = new Parent(); var child1 = new Child(); console.log(child1.getName()) // kevin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function Parent () {
    this.name = 'kevin';
}
 
Parent.prototype.getName = function () {
    console.log(this.name);
}
 
function Child () {
 
}
 
Child.prototype = new Parent();
 
var child1 = new Child();
 
console.log(child1.getName()) // kevin

问题:

1.引用类型的属性被所有实例共享,举个例子:

function Parent () { this.names = ['kevin', 'daisy']; } function Child () { } Child.prototype = new Parent(); var child1 = new Child(); child1.names.push('yayu'); console.log(child1.names); // ["kevin", "daisy", "yayu"] var child2 = new Child(); console.log(child2.names); // ["kevin", "daisy", "yayu"]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function Parent () {
    this.names = ['kevin', 'daisy'];
}
 
function Child () {
 
}
 
Child.prototype = new Parent();
 
var child1 = new Child();
 
child1.names.push('yayu');
 
console.log(child1.names); // ["kevin", "daisy", "yayu"]
 
var child2 = new Child();
 
console.log(child2.names); // ["kevin", "daisy", "yayu"]

2.在创建 Child 的实例时,不能向Parent传参

JavaScript继承方式总结

继承:超类构造函数中有属性有方法,超类原型中有属性有方法,子类想要继承超类的构造函数,超类原型中的部分属性和方法,于是便有了继承。

  • 原型链继承:讲一个类型的实例赋值给另一个构造函数的原型,子类型就能够访问超类型所有的属性和方法
//原型链的继承
//缺点:对象实例共享所有的属性和方法,因此不适合单独使用。
function Parent(){
    this.name="mike";
}
function Child(){
    this.age=19;
}
Child.prototype=new Parent();//子类原型等于父类实例
var test =new Child();
console.log(test.age);
console.log(test.name);

function Brother(){
    this.weight=60;
}
Brother.prototype=new Child();
var brother=new Brother();
console.log(brother.name);
console.log(brother.age);```

- 借用构造函数模式

//借用构造函数/类式继承call()/apply()
//可以传递参数,但是方法无法共享
function Parent(age){
this.name=['mike','jack','smith'];
this.age=age;
}
function Child(age){
Parent.call(this,age);
}
var test=new Child(21);
console.log(test.age);//21
console.log(test.name);//mike,jack,smith
test.name.push('bill');
console.log(test.name);//mike,jack,smith,bill

//call()和apply()用法区别
The difference is that apply lets you invoke the function with arguments as an array;
call requires the parameters be listed explicitly.
A useful mnemonic is "A for array and C for comma(逗号)."```

  • 组合式继承:原型链和构造函数结合的方法,原型链继承共享的属性和方法,构造函数继承实例属性。
//组合式继承
//组合构造函数和原型链
//原型链继承原型属性和方法,构造函数实现实例属性的继承
function Parent(name){
    this.name=name;
    this.arr=['aaa','bbb','ccc'];
}

Parent.prototype.run=function(){
    return this.name;
};

function Child(name,age){
    Parent.call(this,age);//第二次调用
    this.age=age;
}

Child.prototype=new Parent();//第一次调用```

- 原型式继承:不必预先定义构造函数的情况下实现继承,本质是执行给定对象的浅复制,而复制的副本还可以得到进一步的改造。

//借助于原型并基于已有的对象创建新对象,同时还不用创建自定义类型
function obj(o){
function F(){}
F.prototype=o;
return new F();
}
var box={
name:"masike",
arr:['baba','mama','didi']
};
var b1=obj(box);
console.log(b1.name);//masike

b1.name='mike';
console.log(b1,name);//mike

console.log(b1,arr);//baba,mama,didi
b1.arr.push("parents");
console.log(b1.arr);//baba,mama,didi,parents

var b2=obj(box);
console.log(b2.name);//masike
console.log(b2.arr);//baba,mama.didi,parents

- 寄生式继承:基于某个对象后某些信息创建一个对象,然后增强对象,最后返回对象。

function create(o){
var f=obj(o);
f.run=function(){
return this.arr;
}
return f;
}```

  • 寄生组合式继承:集寄生式继承和组合是继承优点于一身,是实现基于类型继承的最有效的方式。解决组合继承模式由于多次调用父类构造函数而导致低效率问题。
//寄生组合式类型
//解决了父类构造函数两次调用问题
function obj(o){  //(原型式)
    function F(){}
    F.prototype=o;
    return new F();
}
function create(parent,test){
    var f=obj(parent.prototype);//创建对象
    f.constructor=test;//增强对象
}
function Parent(name){
    this.name=name;
    this.arr=['brother','sister','parents'];
}

Parent.prototype.run=function(){
    return this.name;
}
function Child(name,age){
    Parent.call(this,name);
    this.age=age;
}
Child.prototype = obj(Parent.prototype);//实现继承

var test=new Child("masike",19);
test.arr.push("withershins");
console.log(test.arr);
console.log(test.run());//只共享了方法

var test2=new Child("jack",22);
console.log(test2.arr);//引用问题解决```

未完待续......
>继承最推荐的解决方案:

         if(!Object.create){//object.create()是ES5新增方法
                Object.create= (function(){
                    function F(){}   //创建中介函数(bridge)
                    return function(obj) {
                        if(arguments.length !== 1) {
                            throw new Error("仅支持一个参数");
                        }
                        F.prototype = obj;   //原形绑定
                        return new F();      //返回实例
                    }
                })()
        //最终返回的结果,既是F的实例属性,享有F构造函数中的所有属性和方法(因为F构造函数为空,所以完全不用担心会有多余不想要的属性方法存在),[[prototype]]又指向F.prototype,返回的结果是一个对象!!!
        }
        function Person(name, age) {
                this.name = name;
                this.age = age;
        }
        Person.prototype.walk = function() {//写到了prototype中,walk一定是想要共享的方法
                console.log("走路....");
        } 
        function Child(name, age, address) {
                Person.call(this, name, age);//这里继承了person构造函数中想要传递的一些属性
                this.address = address;
        }
        Child.prototype = Object.create(Person.prototype);//不要再使用new了!
        Child.prototype.talk = function() {
            console.log("说话ing.....")
        }
        //不用new的原因是因为你不想要Child继承Person构造函数中的所有属性和方法,而是想让他单独继承Person.prototype中共享的属性和方法。```
TAG标签:
版权声明:本文由必威发布于必威-前端,转载请注明出处:也许有点难懂,继承讲解