js-原型

34 阅读10分钟

参考:

前述

参考:

创建对象的方式

  • 字面量或则new Object()

  • 缺点:创建大量相似对象时,会产生大量重复的代码;

  • 工厂模式

  • 缺点:都是new Object();来的,不同工厂创建的对象无法区分是什么类;

  • 构造函数

  • 优点:所创建的对象和构造函数建立起了联系,因此我们可以通过原型来识别对象的类型;

  • 缺点:造成了不必要的属性函数的创建;如果对象属性中如果包含函数的话,那么每次我们new时都会新建一个函数对象,浪费了不必要的内存空间,因为函数是所有的实例都可以通用的。

  • 原型模式

  • 优点:在原型prototype上添加公用的属性和方法,从而实现代码的复用,相对于构造函数来说,解决了属性为函数时复用的问题;

  • 缺点

  • 没有办法通过传入参数来初始化值

  • 如果在prototype上添加一个引用类型如Array,么所有的实例将共享一个对象,一个实例对引用类型值的改变会影响所有的实例。

  • 组合使用构造函数和原型

  • 优点:解决了构造函数和原型模式单独使用时的缺点;因此我们可以组合使用这两种模式,通过构造函数来初始化对象的属性,通过原型对象来实现函数方法的复用。

  • 因为使用了两种不同的模式,所以对于代码的封装性不够好,代码都不在一起;

  • 动态原型模式

  • 优点:解决了组合模式中封装性的问题

  • 寄生构造函数【特殊】

  • 优点: 封装性比较好,它主要是基于一个已有的类型,在实例化时对实例化的对象进行扩展。这样既不用修改原来的构造函数,也达到了扩展对象的目的。

  • 缺点:和工厂模式一样的问题,不能依赖 instanceof 操作符来确定对象的类型。

  • 稳妥构造函数【特殊】

  • 优点:提高变量安全性,私有化属性,实例只能通过方法取得

  • 缺点:和工厂函数一样,没有办法使用 instanceof 操作符来判断对象的类型;而且带来了闭包问题;

对象代码:

1.字面量
const obj = {
	name:"zhangsan",
  age: 18,
  sayName: function(){
  	console.log(this.name)
  }
}

2. 工厂函数
function createPerson(){
	let obj = {}
  obj.name = name
  obj.age = age
  obj.sayName = function(){
  	console.log(obj.name)
  }
  return obj;
}
function createDog(name, age) {
  var obj = new Object()
  obj.name = name
  obj.age = age
  obj.sayHello = function () {
    alert('汪汪~~')
  }
  return obj
}

var obj1 = createPerson('猪八戒', 28)
var obj2 = createPerson('白骨精', 16)

var dog = createDog('旺财', 3)
// 缺点:人狗不分了

3.构造函数
function Person(name, age, gender) {
  this.name = name
  this.age = age
  this.gender = gender
  this.sayName = function () {
    alert(this.name)
  }
}
function Dog() {}

var per = new Person('孙悟空', 18, '男')
var per2 = new Person('玉兔精', 16, '女')

// 优点:人狗分开
console.log(per instanceof Person);
console.log(dog instanceof Person);

// 缺点每次都会创建新的函数,浪费内存
console.log(per.sayName === per2.sayName) // false

4.原型模式
// 缺点1:没有办法传参数
function Person(){}
Person.prototype.name = "张三"
Person.prototype.sayName = function(){ 
	console.log(this.name)
}
Person.prototype.myList = []  // 缺点:所有实例公用,一个实例对其修改,另外的数据也变了

const  p1 = new Person();
const  p2 = new Person();

 // 优点:每次创建都一个函数,优化内存
console.log(p1.sayName === p2.sayName) // true


5. 组合使用构造函数和原型
function Person(name,list){
	this.name = name
  this.myList = list
}
Person.prototype.sayName = function(){  
	console.log(this.name)
}
或则
Person.prototype = {
  constructor: Person,
  sayName: function(){
    alert(this.name);
  }
}
// 优点:解决构造函数和原型模式的创建对象的缺点
// 缺点:封装性不好

6.动态原型模式
function Person(name, age, job){
  this.name = name;
  this.age = age;
  this.job = job;

  if(typeof this.sayName !== "function" ){
    Person.prototype.sayName: function(){
      alert(this.name);
    } 
  } 
}
var p = new createPerson("james"9"student");
p.sayName(); // "james"

注意在 if 语句中检查的可以是初始化后应该存在的任何属性或方法,不必要检查每一个方法和属性,只需要检查一个就行。

优点:解决了组合模式中封装性的问题

7.寄生构造函数模式
案列1
function Person(name, age, job){
  var o = new Object();
  o.name = name;
  o.sayName = function(){
    alert(this.name);
  };
  return o;
}

var p = new Person("james",9,"student");
console.log(p instanceof Person) // false

案列2
function SpecialArray() {
  var values = new Array;
  values.push.apply(values, arguments);
  values.toPipedString = function() {
    return this.join("|");
  };
  return values;
}
var colors = new SpecialArray("red", "green", "blue");
console.log(colors.toPipedString());

这个模式可以在特殊的情况下用来为对象创建构造函数。假设我们想创建一个具有额外方法的特殊数组。
由于不能直接修改 Array 构造函数,因此可以使用这个模式。
与工厂函数的区别在于这是new的,而不是 函数调用;

比如为Array扩展一些方法,如果直接在Array的原型对象上做扩展,那么就会污染其他的数组。
寄生就意味着这个新的构造函数是在Array构造函数的基础上继续修改的。
最后,加上new是为了让人清楚实例是新(被改造的)构造函数的实例,
这也是名字中带有构造函数四个字的原因。以上,寄生构造函数模式的意义。。

优点:我对这个模式的理解是,它主要是基于一个已有的类型,在实例化时对实例化的对象进行扩展。
这样既不用修改原来的构造函数,也达到了扩展对象的目的。

缺点:和工厂模式一样的问题,不能依赖 instanceof 操作符来确定对象的类型。


8.稳妥构造函数模式
function Person(name, age, job){
    var o = new Object();
    //可以在这里定义私有变量和函数
  	let myName = name

    o.sayName = function(){
        console.log(myName);
    }
    return o;
} 

var p1 = Person('james', 9, 'student')
var p2 = Person('qwe', 9, 'student')
p1.sayName() // "james"
p2.sayName() // "qwe"


优点:以上面为例,除了 sayName 方法外,没有别的方法可以访问数据成员,这就是稳妥构造函数提供的安全性。

缺点:和寄生构造函数一样,没有办法使用 instanceof 操作符来判断对象的类型;
而且带来了闭包问题;

说明

理解了就非常简单啦。其实就是下面内容:

概念:

  • 构造函数

  • 实例对象

  • 原型对象

  • 显示

  • 隐式

  • 构造器constructor

  • 内置对象

  • Object

  • Function

结果:

  • 实例属性和方法

  • 静态属性和方法

  • 原型对象的属性和方法

构造函数

就是普通的函数的定义写法,习惯大写。被new调用就叫做构造函数。

每个 JavaScript 函数实际上都是一个 Function 的实例对象。

(function(){}).constructor === Function // true

function MyClass() {}  // 构造函数

var mc = new MyClass(); // 实例对象
// 相当于
var mc = new MyClass.prototype.constructor

MyClass.prototype.constructor === MyClass  // true

构造函数返回

  • 如果是基本类型,还是原型那一套,返回值在实例中不起作用。

  • 如果是对象,那就不是原型那一套了,就直接是这个对象了。

new的过程

  1. 创建一个空对象 newObject = null;

  2. 设置原型,将对象的(隐式)原型设置为函数的 prototype 对象。newObject = Object.create(constructor.prototype);

  3. 将 this 指向新建对象,并执行函数;result = constructor.apply(newObject, arguments);

  4. 判断函数的返回值类型,如果是值类型,返回创建的对象。如果是引用类型,就返回这个引用类型的对象。

    function objectFactory() { let newObject = null, constructor = Array.prototype.shift.call(arguments), result = null;

    // 参数判断 if (typeof constructor !== "function") { console.error("type error"); return; }

    // 新建一个空对象,对象的原型为构造函数的 prototype 对象 newObject = Object.create(constructor.prototype);

    // 将 this 指向新建对象,并执行函数 result = constructor.apply(newObject, arguments);

    // 判断返回对象 let flag = result && (typeof result === "object" || typeof result === "function");

    // 判断返回结果 return flag ? result : newObject; }

    // 使用方法 // objectFactory(构造函数, 初始化参数);

原型

概述

每个、任何函数都会被环境加在一个属性prototype,指向一个原型对象

该函数作为普通函数使用时,该原型没有啥作用。可以使用该函数的构造函数来创建一个对象实例,该实例对象中有隐藏属性指向上述的原型对象。该隐藏属性是没有办法直接获取的,一些浏览器环境给加上了__proto__来解决,但是最好不要直接用__proto__来做什么操作,可以用Object.getPrototypeOf(实例)代替。但是也是不符合规范的,最正确的做法应该是使用Ocject.create()来继承一个原型;

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>测试原型对象</title>
</head>

<body>
  <script>
    function MyClass() {}

    var mc = new MyClass();
    var mc2 = new MyClass();
    console.log(1,mc.__proto__ === MyClass.prototype)
    console.log(2,mc2.__proto__ === MyClass.prototype)

    console.log(3,MyClass.prototype);
    console.log(4,MyClass.__proto__);
    console.log(5,mc.__proto__);
    console.log(6,mc.prototype);

    console.log(7,MyClass.__proto__ === MyClass.prototype)
  </script>
</body>

</html>


1 true
2 true
3 {constructor: ƒ}
4 ƒ () { [native code] }
5 {constructor: ƒ}
6 undefined

这个连接是存在与实例与构造函数的原型对象之间的,而不是存在于实例和构造函数之间的

每个函数都有一个原型对象,默认什么属性都没有,开发者可以自己添加。但是原型对象也有原型,默认指向根对象的原型对象,就是Object的原型对象,这上面有一些对象的属性和方法。

实例对象都只有__proto__,函数才有prototype,函数的__proto__是一个函数类型(Function.prototype)。

Object的(显式)原型的(隐式)原型是null。

首先:

typeof Object === 'function'

  • 每个函数都有一个prototype属性,它默认指向一个空对象(即称:原型对象)

  • 空对象:没有开发者自己的属性

  • 每个函数的prototype都是指向一个独立的对象

  • js的内置对象指向的可不是一个空对象哦,有一些toString()、hasOwnProperty()等方法

  • 原型对象中有一个属性constructuor,他指向这个构造函数本身

为原型对象添加属性

function Person(){}

// 为原型对象添加方法
Person.prototype.sayName = function(){
    alert(this.name);
}

// 为原型对象添加属性
Person.prototype.name = "张三"

原型的结构(node环境)

构造函数

function Person(){}

// 为原型对象添加方法
Person.prototype.sayName = function(){
    alert(this.name);
}

实例

var student = new Person();

为函数对象添加属性

相当于静态成员,与实例属性是不互通的。

function Phone(){}

Phone.name = "手机"
Phone.change = function(){
	console.log("我可以改变世界")
}
Phone.prototype.size = "5.5inch"

let nokia = new Phone()

console.log(nokia.name)  // undefined
console.log(nokia.size)  // 5.5inch

关于Function的原型

不管了,没看懂

  • 所有函数的隐式原型都是一样的,都是Function.prototype

  • 这是说的是函数的隐式原型,不是实例对象哈

    测试原型对象

显式隐式原型

  • 每个函数function都有一个prototype,即显示原型(属性)

  • 每个实例对象都有一个__proto__,可称为隐式原型(属性)

  • 对象的隐式原型的值为其对应构造函数的显示原型的值

  • 每个函数function也有一个__proto__属性,function是Function的实例对象

  • 函数的prototype属性:在定义函数时自动添加的,默认值是一个“空”对象Object

  • 实例对象的__proto__属性:创建对象时自动添加的,默认值为构造函数的prototype属性值

  • 程序员能直接操作显示原型,但不能直接操作隐式原型(ES6之前)

隐式原型是对于实例对象来说的

显示原型是对于构造函数来说的

原型链

  • 原型链的尽头是Object的显式原型对象,再后就是null了

  • 原型链本质上是隐式原型链

  • 原型链的作用:查找对象的属性

  • 对象.xxx就是去查找, 是查找的隐式原型链

属性修改

注意修改实例的属性和修改原型的属性影响不同。

instanceof原理

a instanceof B

判断实例对象a的隐式原型链上 是否有 B的显示原型

总结

  • 每个构造函数都有一个显示原型prototype属性,它默认指向一个Object空对象(即称:原型对象

  • 空对象:默认没有开发者自己的属性,可以手动添加

  • 每个函数的prototype都是指向一个独立的对象

  • js的内置对象指向的可不是一个空对象哦,有一些toString()、hasOwnProperty()等方法

  • 原型对象中有一个属性constructuor构造器,指向这个构造函数本身

  • 除了Object.prototype外,原型对象都是Object的实例

  • 每个实例对象都被添加了隐式原型__proto__,指向构造函数的原型对象

  • 所有函数都是Function的实例对象(包括Function自己也是)

  • Object的原型对象是原型链的尽头,即Object.prototype

  • 再往下就是null

  • A instanceof B 表示:B函数的显式原型对象prototype是否在A对象的原型链上

简单版:

完全完整版: