在 Javascript 中可以说原型是在原生开发中比较重要的一个概念

要想在原生 JS 开发中得心应手,必须熟悉原型、原型对象、原型属性之间的关系

还要熟悉原型链,以及原型链搜索原则

原型属性

prototype

在js中每当我们创建一个函数的时候就会有一个prototype(原型)属性

这个属性其实是存储的一个引用(指针),指向一个对象,这个对象中有属性和方法

声明一个函数function F(){}

其关系结构为:

在js中, 对象 { } 中包含 toStringvalueOfconstructor

方法, 实际上就是由原型继承来实现的。对象继承自原型对象 Object.property

那么原型又有什么卵用呢?

看一个demo

1
2
3
4
5
6
7
8
9
10
11
12
13
// 声明一个构造函数  内部有一个方法fn
function F(){
this.fn = function(){
console.log('hello world');
};
}
// 实例化两个F对象 : f1和f2
var f1 = new F();
var f2 = new F();
f1.fn(); // => hello world
f2.fn(); // => hello world
// 进行比较两个实例对象的fn方法
console.log(f1.fn === f2.fn); // => false

通过比较两个实例对象的 fn 方法可以得出:每个实例对象的 fn 方法是独立存在的

那么问题就来了?

两个 F 类型实例有同样功能的方法却占用了两块不同的内存空间

这无疑造成了内存的泄露

因此,就有了prototype(原型)的概念

对上面的demo改造一下:

1
2
3
4
5
6
7
8
9
10
11
12
// 声明一个构造函数  内部有一个方法fn
function F(){}
F.prototype.fn = function(){
console.log('hello world');
};
// 实例化两个F对象 : f1和f2
var f1 = new F();
var f2 = new F();
f1.fn(); // => hello world
f2.fn(); // => hello world
// 进行比较两个实例对象的fn方法
console.log(f1.fn === f2.fn); // => true

那么prototype里面又有什么呢?

prototype就是通过调用构造函数( F )创建出来的那个对象( f1f2 )的原型对象

advantage: 资源共享

说白了就是可以让所有的对象实例共用原型对象里面的所有属性和方法

原型对象

__proto__

在创建函数时,其内部的 prototype (原型)属性所指向的那个对象就是原型对象

当我们创建一个对象时,其实是存在两个对象:

一个是当前对象(对象本身),一个是原型对象

即每个对象都有一个 __proto__ (原型)属性,指向其原型对象

对象的原型( __proto__ )就是其构造函数的一个属性( prototype

由某一函数new出来的对象,会自动的链接到该函数的 prototype

看下面的demo:

具体一点也就是:

通过构造函数F创建出来的对象obj,该对象的默认会有一个 __proto__ 属性,

并且该属性会自动链接到构造函数F的原型对象上( F.prototype ),

并且该对象继承了 F.prototype 的所有属性和方法。

即通过对象的__proto__属性可以直接访问到该对象的原型对象

不过该属性是个非标准属性,可能会存在一些浏览器不支持

在开发中不建议使用,后面会介绍一个通用的方法

1
2
3
function F(){}
var obj = new F();
console.log(obj.__proto__ === F.prototype); // => true

constructor

每个对象都可以访问到一个constructor(构造函数)属性

在默认情况下,所有的原型对象都会有这样一个属性

这个属性存储着指向prototype所在函数的引用(指针)

可以这么理解:

其实constructor属性是由该对象的原型对象提供的

constructor属性描述是该对象的构造函数

给之前的一张图添加一个constructor属性

那么现在就可以对上面提到的通过对象的__proto__属性访问其原型对象

做一个通用的处理

1
2
3
function F(){}
var obj = new F();
console.log(obj.__proto__); // 在IE8 中 => undefined

进行浏览器能力检测,不存在添加一个

1
2
3
4
5
6
7
8
if(! {}.__proto__){
Object.prototype.__proto__ = function(){
return this.constructor.prototype;
};
}
function F(){}
var obj = new F();
console.log(obj.__proto__); // ???

获取对象的类型

创建什么对象,所描述的是构造函数的名字

构造函数的名字就是其创建出来的对象的类型名

1
2
function F(){}
var obj = new F();

new一个F,即创建了一个F对象,并把创建的F对象赋值给了obj

也就是实例化了一个F对象obj

有时候在对对象进行操作时需要知道其类型

那么如何获得对象的类型?

1、传统的通过字符串来处理函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function F(){}
var obj = new F();
// 获取其构造函数 + '' 将其转化成字符串形式
var str = obj.constructor + '';
console.log(str); // => function F(){}
// 字符串太长不便操作 把公共的 function和后面的空格 替换成 _
str = str.replace('function ','_');
console.log(str); // => _F(){}
// 获取 _ 和 ( 的索引 以便进行字符串截取
var start = str.indexOf('_');
var end = str.indexOf('(');
// 如果字符串中不存在指定字符 则返回 -1
if(start != -1 && end != -1){
var fnName = str.slice(start + 1, end);
}
// 前后分别加上 | 用于验证是否包含空格
console.log('|' + fnName + '|'); // => |F|

为了获取对象类型,这无疑也太麻烦了

2、利用正则表达式进行匹配获取

1
2
3
4
5
6
7
8
9
10
11
function F123(){}
var obj = new F123();
var str = obj.constructor + '';
var reg = /function (.+)\(/;
console.log(reg.exec(str));
// => ["function F(", "F", index: 0, input: "function F(){}"]
// exec 返回一个数组集合
// 第一项为正则表达式匹配成功的字符串
// 第二项为正则表达式里的第一个分组项匹配成功的字符串。。。
var fnName = reg.exec(str)[1];
console.log('|' + fnName + '|'); // => |F123|

比第一个精简了一点,不过还是不好

3、通过构造函数的name属性获取

1
2
3
4
5
function F111(){}
var obj = new F111();
// 先获取到其构造函数 再获取构造函数的名字
var fnName = obj.constructor.name;
console.log(fnName); // => F111

这个方法很好用 不过IE8之前的浏览器不支持

封装一个通用的方法getFnName

1
2
3
4
5
6
7
function getFnName(obj){
return obj.constructor.name ||
/function (.+)\(/.exec(obj.constructor + '')[1];
}
function F123(){}
var obj = new F123();
console.log(getFnName(obj)); // => F123

对象的属性访问

如果访问对象的属性:

就会先在当前对象中去找

如果没有就去当前对象的原型对象中去找

如果再没有就去原型链上去找

1
2
3
4
5
6
7
function Person(){
this.name = 'haha';
}
Person.prototype.name = '哈哈';
var p1 = new Person();
p1.name = '123';
console.log(p1.name); // => 123

把对象中的name属性去掉

1
2
3
4
5
6
function Person(){
this.name = 'haha';
}
Person.prototype.name = '哈哈';
var p1 = new Person();
console.log(p1.name); // => haha

再把构造函数中的name属性去掉

1
2
3
4
5
6
7
function Person(){
// this.name = 'haha';
}
Person.prototype.name = '哈哈';
var p1 = new Person();
// p1.name = '123';
console.log(p1.name); // => 哈哈

原型对象和原型属性的区别

  • 相同点:

这两个都是属性, 简单说就是存储引用的变量

这两个属性的引用指向同一个对象

本质上:原型对象和原型属性指的是同一个东西

  • 不同点:

原型对象:__proto__ 是站在对象的角度,是该对象的原型对象

原型属性:prototype 是站在构造函数的角度

是该对象的构造函数的原型属性

或是构造函数创建的对象的原型对象

  • effect:

在实现继承的时候,一般都是使用 构造函数的 prototype 属性

在分析结构与验证对象等测试与调试中,会用到 __proto__

继承

继承是面向对象语言的一个比较重要的特性

在js中为了达到资源的共享,引入了继承的概念

继承说白了就是自己没有的东西,别人有,直接拿过来,使其成为自己的

即继承就是:拿来主义

在继承中, 一个对象继承自另一个对象

继承的对象中包含被继承对象的所有成员

那么为什么要用继承呢?

继承可以简化代码,达到复用的效果

减少代码体积

便于后期的维护

实现继承的方式

原型式继承

如果需要让一个对象有某一行为(属性、方法)

那么可以考虑将这个行为加到原型对象中

那么这个对象就继承自原型对象,获得了该行为

什么是原型式继承?

即对象继承自其原型对象

所谓的原型式继承就是在对象的原型对象中加内容即可

  • 利用动态添加属性实现

    1
    2
    3
    function F(){}
    F.prototype.fn = function(){ console.log('大家好'); };
    var obj = new F();
  • 覆盖原型

    1
    2
    3
    4
    5
    6
    7
    function F(){}
    F.prototype = {
    name : '哈哈',
    say : '你好',
    hello : 'hello'
    };
    var obj = new F();

    这时obj的原型对象就被覆盖掉了

    同时原型对象中的constructor也不存在了

    接下来,再复杂一点:

    1
    2
    3
    4
    5
    6
    7
    8
    function F(){}
    var obj1 = new F();
    F.prototype = {
    name : '哈哈',
    say : '你好',
    hello : 'hello'
    };
    var obj2 = new F();

    可以画出关系图:

    note:通过var obj1 = new F();实例化一个对象时

    就会自动链接到Fprototype属性上,即obj1的原型对象上

    F.prototype = {...};,这时的F.prototype的引用就指向了一个新赋值的对象了

    此时的obj1的原型对象依然存在,而且还是原始的原型对象

  • 组合式 原型继承 extend
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    function F(){}
    F.fn = F.prototype;
    F.fn.extend = function(obj){
    for(var k in obj){
    this[k] = obj[k];
    }
    };
    var obj1 = new F();
    var obj2 = {
    name: '哈哈',
    age: 22,
    say: 'haha'
    };
    F.fn.extend(obj2);
    console.log(obj1);
    console.log(obj2);

组合式继承 添加成员 extend

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var o1 = {
name: 'haha',
age: 22
};
var o2 = {
say: '你好'
};
o2.extend = function(obj){
for(var k in obj){
this[k] = obj[k];
}
};
o2.extend(o1);
console.log(o1); // => Object {name: "haha", age: 22}
console.log(o2); // => Object {say: "你好", name: "haha", age: 22}

经典的继承

1
2
3
4
5
6
7
8
9
10
11
12
13
function create(obj){
if(Object.create){
return Object.create(obj);
}else {
function F(){}
F.prototype = obj;
return new F();
}
}
var o1 = {
name: 'haha'
};
var o2 = create(o1);

构造函数

构造函数就是函数的一种特殊表现形式

任何函数都可以做构造函数来使用

effect:初始化对象

meaning:代码的复用

new就是在创建对象

让一个对象从无到有

即实例化这个构造函数

对象的属性由构造函数决定的

对象的原型对象由构造函数的prototype属性决定的

函数的prototype所指向的对象的constructor指向当前函数

构造函数如何初始化,就看 this

在没有构造函数时给对象添加成员(属性或方法),往往采用: 对象.成员 = 值

创建对象的整个过程:

1、new申请内存空间

2、调用构造函数, 构造函数有一个隐式参数,即 this

3、刚创建出来的对象的引用 赋值给 this,由构造函数处理

4、在构造函数中利用 this.成员 = 值 来给对象添加成员

function Person() {}

conclusion

1、定义函数,函数会有一个prototype属性

2、函数的prototype属性是一个对象类型

3、prototype属性是一个含有constructor__proto__属性的对象

这个对象就是Person.prototype

4、constructor属性就是当前函数

deduction:函数的 prototype 属性的 constructor 就是当前函数

5、所有的函数的 prototype 属性所表示的对象都继承自 Object.prototype

即:函数.prototype.__proto__ 就是 Object.prototype

通过构造函数创建对象和对象字面量创建对象的区别?

对象字面量创建出来的对象的原型对象是Object.prototype

而通过构造函数创建出来的对象的原型对象是函数.prototype

advantage:

1、通过构造函数创建出来的对象具有独立的原型对象,只影响到当前原型对象

2、当前对象有类型名了

直接继承和间接继承

在js中,每一个对象都有一个原型对象

并且每一个原型对象都直接,或间接的继承自 Object.prototype 对象

那什么是直接继承呢?

看下面的demo

1
2
function Person(){}
var p = new Person();

这时Person的实例p对象的原型对象就直接继承自Object.prototype 对象

再看下面的demo

1
2
3
4
5
function Animal(){}
function Person(){}
var a = new Animal();
Person.prototype = a;
var p = new Person();

此时Personprototype引用被修改,指向了a对象

a对象的原型对象继承自Object.prototype 对象

因此,Person的实例p对象的原型对象就间接继承自Object.prototype 对象

原型链

在创建一个对象的时候,除了当前对象有原型对象

原型对象也有原型对象

原型对象的原型对象也有原型对象

于是就形成了一条链式结构,这条链式结构就叫原型链

conclusion:每个对象都有一条最终指向null的原型链

1
2
function Fn(){}
var f1 = new Fn();

对象f1的原型链结构如下图所示:

对象的属性访问

如果访问对象的属性:

就会先在当前对象中去找

如果没有就会去该构造函数中去找

如果再没有就去该构造函数的原型属性中去找

如果还是没有就会在当前对象的原型链上去找,直到找到null

1
2
3
4
5
6
7
function Person(){
this.name = 'haha';
}
Person.prototype.name = '哈哈';
var p1 = new Person();
p1.name = '123';
console.log(p1.name); // => 123

把对象中的name属性去掉

1
2
3
4
5
6
function Person(){
this.name = 'haha';
}
Person.prototype.name = '哈哈';
var p1 = new Person();
console.log(p1.name); // => haha

再把构造函数中的name属性去掉

1
2
3
4
5
6
7
function Person(){
// this.name = 'haha';
}
Person.prototype.name = '哈哈';
var p1 = new Person();
// p1.name = '123';
console.log(p1.name); // => 哈哈

对象通过初始化来的属性的访问顺序在对象.属性动态添加的之后访问

即先访问对象.属性,在访问初始化来的属性

Object.prototype中的成员

hasOwnProperty()

effect: 检测属性是否是自己原生的,如果被继承返回false

grammar:boolean对象.hasOwnProperty(属性名);

1
2
3
4
5
6
7
8
9
10
11
12
13
var o = {name:'haha'};
console.log(o.hasOwnProperty('name')); // => true

function F(){
this.name = 'haha';
}
var obj = new F();
obj.age = 22;
console.log(obj.hasOwnProperty('age')); // => true
console.log(obj.hasOwnProperty('name')); // => true
F.prototype.sex = '男';
console.log(obj.sex); // => 男
console.log(obj.hasOwnProperty('sex')); // => false

isPrototypeOf()

grammar:booleanobj1.isPrototypeOf(obj2);

effect:判断对象obj1是否为对象obj2的原型对象

1
2
3
4
5
6
7
8
9
var o1 = {name:'haha'};
var o2 = {name:'haha'};
console.log(o1.isPrototypeOf(o2)); // => false
console.log(Object.prototype.isPrototypeOf(o1)); // => true

function F(){}
F.prototype = o1;
var obj = new F();
console.log(o1.isPrototypeOf(obj)); // => true

propertyIsEnumerable()

-able 表示可能性的后缀

enum 枚举,一个一个的展示出来

effect: 属性是否可以枚举 即属性是否可以被 for-in 遍历

并且同时判断属性是否是自己提供的(两者必须同时满足)

grammar: booleanobj.propertyIsEnumerble(属性名);

  • toString

effect: 将其他类型数据转换成 string 类型

grammar: string其他类型数据.toString([number])

note: 可以跟参数,只有在 number 类型的数据转换时参数才有效

参数代表 number 类型将要转换的进制数,转换过后同样也是 string 类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var num = 12;
console.log(num.toString()); // => 12
console.log(num.toString(2)); // => 1100
console.log(typeof num.toString(2)); // => string

var bool = true;
console.log(bool.toString(2)); // => true

var str = '123';
console.log(str.toString(2)); // => 123

console.log([].toString()); // => 空
console.log([1, 2, 3].toString()); // => 1,2,3

console.log({}.toString()); // => [object Object]
console.log({name: '哈哈', age: 22}.toString()); // => [object Object]

console.log(Array.toString()); // => function Array() { [native code] }
console.log(Date.toString()); // => function Date() { [native code] }

toLocaleString

toLocaleStringtoString 的用法和作用基本相同

不同的是 toLocaleString 是转换为本地字符串

日期类型不同

1
2
3
4
var time = new Date();
console.log(time); // => Wed Mar 30 2016 09:57:32 GMT+0800 (中国标准时间)
console.log(time.toString()); // => Wed Mar 30 2016 09:57:32 GMT+0800 (中国标准时间)
console.log(time.toLocaleString()); //=> 2016/3/30 上午9:57:32

valueOf

当对象需要转换为数字类型的时候就会调用该方法

该方法与 toString 类似

1
2
3
4
5
6
7
8
9
10
11
12
13
var o = {};
var num = {};
o.toString = function(){
console.log('调用了toString');
};
o.valueOf = function(){
console.log('调用了valueOf');
};
console.log(o - 1); // => 调用了valueOf NaN
console.log(o + ''); // => 调用了valueOf undefined
console.log(Number(o)); // => 调用了valueOf NaN
console.log(String(o)); // => 调用了toString undefined
console.log(Boolean(o)); // => true

instanceof

grammar: boolean对象 instanceof 构造函数

effect: 判断一个对象是否是该构造函数的实例

conclusion: 任意的一个对象都有一条原型链,只要在该链上的构造函数,instanceof返回true

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
console.log([] instanceof Array);          // => true
console.log([] instanceof Object); // => true
console.log([] instanceof Function); // => false

console.log(Array instanceof Function); // => true
console.log(Date instanceof Function); // => true

console.log(Object instanceof Function); // => true
console.log(Function instanceof Object); // => true

function Fn(){}
console.log(Fn instanceof Function); // => true
console.log(Fn instanceof Object); // => true

var f = new Fn();
console.log(f instanceof Fn); // => true
console.log(f instanceof Function); // => false

function Foo(){}
var o = new Foo();
Fn.prototype = o;
var f2 = new Fn();
console.log(f2 instanceof Foo); // => true

最后一个案例的结构图如下:

这也验证了上述的结论,只要构造函数在当前对象的原型链上

即:当前对象 instanceof 构造函数 = true

函数

函数是js的一等公民

函数也是一个对象

函数也是一种数据类型

{ }123'haha'true一样是有类型的数据

即:函数可以作为参数传递

创建函数的方法:

  • 声明式
    • function Fn(){ }
  • 表达式
    • var fn = function(){ };
  • 通过Function
    • var fn = new Function();
1
2
3
4
5
6
function Fn(){}
console.log(Fn instanceof Function); // => true
console.log(Array instanceof Function); // => true
console.log(Date instanceof Function); // => true
console.log(Object instanceof Function); // => true
console.log(Function instanceof Function); // => true

conclusion: 任意函数都是Function的实例,Function自己也是自己的实例

Function

grammar: var fn = new Function(arg1, arg2, arg3, ..., body);

note:

Function 构造函数所有的参数都是字符串类型的

除了最后一个参数,所有的参数都作为生成函数的参数

这里可以没有前面的参数

最后一个参数也是字符串,表示的是生成函数的函数体

看下面两个等价函数:

1
2
3
4
5
function fn(a, b){
return a + b;
}

var fn = new Function('a', 'b', 'return a + b;');

Function还有一个作用就是:将字符串当作js代码执行

eval也是可以将字符串当作js代码进行执行

grammar : eval( str );

Functioneval 都可以实现字符串当作js代码执行

在实际开发中, 如果考虑效率可以直接使用 eval

但是考虑安全性则建议使用 Function

eval 会污染全局作用域

  • conclusion :
    • 1、 对象都有原型对象, 对象默认继承自原型对象
    • 2、 函数被定义后, 默认就有原型属性, 原型属性也是对象
    • 3、 函数的原型属性默认继承自 Object.prototype
    • 4、 原型对象中 constructor 指向对应的构造函数
    • 5、 所有的函数都是 Function 的实例
    • 6、 函数也是对象

默认函数的原型属性继承自 Object.prototype

Function 是函数,Function.prototype 是函数的原型属性

Function.prototype 继承自 Object.prototype

ArrayFunction 的实例, 继承自 Fucntion.prototype

DateFunction 的实例, 继承自 Fucntion.prototype

ObjectFunction 的实例, 继承自 Fucntion.prototype

FucntionFunction 的实例, 继承自 Fucntion.prototype

conclusion : Function.__proto__就是 Function.prototype

最终可以得到函数的原型链:

添加

函数和继承都可以实现代码的复用

函数是面向过程的思想

而继承是面向对象的思想

属性与方法有一个特点是 :和当前对象息息相关的

这些与当前对象息息相关的属性与方法就称为 : 实例属性与实例方法

那么再函数中, 也可以添加属性与方法

function Foo() {}

Foo.name = 'tom';

这个属性对于 Foo 的实例来说是全局的, 和个体对象没有关系

称这样的属性与方法为 :静态属性与静态方法

实例成员与对象息息相关

而静态成员一般作为通用的工具性的成员使用

{ } :有两重意思

1、代码块 (代码块后面不加分号)

2、对象

代码块:{ }

语句: ;

表达式:

文章目录
  1. 1. 原型属性
    1. 1.1. prototype
  2. 2. 原型对象
    1. 2.1. __proto__
    2. 2.2. constructor
  3. 3. 获取对象的类型
  4. 4. 对象的属性访问
  5. 5. 原型对象和原型属性的区别
  6. 6. 继承
    1. 6.1. 实现继承的方式
      1. 6.1.1. 原型式继承
      2. 6.1.2. 组合式继承 添加成员 extend
      3. 6.1.3. 经典的继承
  7. 7. 构造函数
  8. 8. 直接继承和间接继承
  9. 9. 原型链
  10. 10. 对象的属性访问
  11. 11. Object.prototype中的成员
    1. 11.1. hasOwnProperty()
    2. 11.2. isPrototypeOf()
    3. 11.3. propertyIsEnumerable()
    4. 11.4. toLocaleString
    5. 11.5. valueOf
    6. 11.6. instanceof
  12. 12. 函数
  13. 13. 添加