Hibop / Hibop.github.io

Hibop 个人博客
https://hibop.github.io/
23 stars 1 forks source link

关于js对象和原型继承 #46

Open Hibop opened 5 years ago

Hibop commented 5 years ago

内容提要

面向对象的三个基本特征是:封装、继承、多态。

o_oobase

对于有基于类的语言经验 (如 Java 或 C++) 的开发人员来说,JavaScript 有点令人困惑,因为它是动态的,并且本身不提供一个class实现相关类的继承。全靠一种很奇特的"原型链"(prototype chain)模式,来实现类的继承扩展。(在 ES2015/ES6 中引入了class关键字,但只是语法糖,JavaScript 仍然是基于原型的)。


Javascript继承机制和prototype设计

历史说起 http://blog.vjeux.com/2011/javascript/how-prototypal-inheritance-really-works.html

  1. 1994年,网景公司(Netscape)发布了Navigator浏览器0.9版。急需一种网页脚本语言,使得浏览器可以与网页互动。
  2. 工程师Brendan Eich负责开发这种新语言。他觉得,没必要设计得很复杂:Javascript里面所有的数据类型都是对象(object),这一点与Java非常相似。但是,他随即就遇到了一个难题,到底要不要设计"继承"机制呢————Javascript里面都是对象,必须有一种机制,将所有对象联系起来。
  3. 他不打算引入"类"(class)的概念,因为一旦有了"类",Javascript就是一种完整的面向对象编程语言了,这好像有点太正式了,而且增加了初学者的入门难度。然后引入了轻量级self语言类似的原型继承,然后14天憋出了javascript这门伟大的语言。

最终js面向对象的设计

1.引入new 从构造函数constructor(相当于类class)到实例

function GirlFriend (name) {
  // this.gender = '女';
  this.name = name;
}

var lujisGirl = new GirlFriend('庄颜'); // 《三体》罗辑的梦中女神
var jdsGirl = new GirlFriend('奶茶');

2.prototype属性的引入 构造函数生成实例对象,有一个缺点,那就是无法共享属性和方法。无法做到数据共享,也是极大的资源浪费。 比如:女神都有的特点——美、体贴、善良的女性...

function GirlFriend (name) {
  // this.gender = '女';
  this.name = name;
}
GirlFriend.prototype.gender = '女';

var lujisGirl = new GirlFriend('庄颜');
var jdsGirl = new GirlFriend('奶茶');
console.log(lujisGirl.gender, jdsGirl.gender); // '女' '女'

GirlFriend.prototype.gender = '男'; // 假设
console.log(lujisGirl.gender, jdsGirl.gender); // '男' '男'

demo

实例对象共享构造函数的同一个prototype对象, 这样便实现了"继承"。


原型

1.在JavaScript中创建函数时,JavaScript引擎会向函数添加prototype属性。此prototype属性是一个对象(称为原型对象),默认情况下具有构造函数属性。构造函数属性指向原型对象是属性的函数。

Fn.prototype.constructor === Fn

2.JavaScript创建实例对象时(new constructor(), 赋值创建, Object.create()除外),JavaScript引擎会将__proto__属性添加到新创建的对象中,该对象称为dunder proto魔术__proto__ (php/python有此概念))。proto指向构造函数的原型对象。

obj.__proto__ ===  Fn.prototype

原型链查找

function getProperty(obj, prop) {
  if (obj.hasOwnProperty(prop))
    return obj[prop]

  else if (obj.__proto__ !== null)
    return getProperty(obj.__proto__, prop)

  else
    return undefined
}

回到myGirl栗子:lujisGirl(实例) --> GirlFriend.prototype --> Object.prototype --> null


结论:

links

new背后的故事

// 伪代码
function new(GirlFriend(args)) {
  var obj = {}; // 创建空对象
  obj.__proto__ = GirlFriend.prototype; // 建立原型链[继承]
  var result = GirlFriend.call(obj, args); // 构造函数执行,将this-> obj,  非继承属性 this.name执行
  return typeof result === 'object'? result : obj; // 判断构造函数return: 无返回值 或者 返回一个非对象值
}

stackoverflow上一个人的回答来:

In JavaScript, most functions are both callable and instantiable: they have both a [[Call]] and [[Construct]] internal methods.

在JS中,绝大多数的函数都是既可以调用也可以实例化的.我们既可以直接执行函数得到函数的返回值.也可以通过new操作符得到一个对象.


JavaScript 世界万物诞生记

class

要点概括:


Object作为一个机器可以看做是有由Function制造出来的,而Function作为一个对象可以看做是由Object制造出来的。

搞懂下边一张经典图,便搞懂js对象世界 image

// 看成对象
Object instanceof Function;
Function instanceof Function;
Object instanceof Object;
Function instanceof Object;

Object.__proto__ === Function.prototype;
Function.__proto__ === Function.prototype;

Object.__proto__.__proto__ === Object.prototype;
Function.__proto__.__proto__ === Object.prototype;

// 到顶级
Object.__proto__.__proto__.__proto__
=== Function.__proto__.__proto__.__proto__
=== null;

js对象的类型判断

  1. typeof sth;
  2. obj instanceof constructor; // 构造函数实例
  3. Object.prototype.toString.call(obj) // 最靠谱

    export function type(obj) {
    const toString = Object.prototype.toString;
    const map = {
    '[object Boolean]': 'boolean',
    '[object Number]': 'number',
    '[object String]': 'string',
    '[object Function]': 'function',
    '[object Array]': 'array',
    '[object Date]': 'date',
    '[object RegExp]': 'regExp',
    '[object Undefined]': 'undefined',
    '[object Null]': 'null',
    '[object Object]': 'object'
    };
    
    return map[toString.call(obj)];
    }

HTML元素的prototype

HTMLDivElement.prototype
             |
             |.__proto__
             |
    HTMLElement.prototype
             |
             |.__proto__
             |
      Element.prototype
             |
             |.__proto__
             |
       Node.prototype
             |
             |.__proto__
             |
      EventTarget.prototype
             |
             |.__proto__
             |
      Function.prototype
             |
             |.__proto__
             |
      Object.prototype
             |
             |.__proto__
             |
           null

js属性方法整理,class语法糖(es6+)

首先问自己:静态属性、原型属性、实例方法、构造函数、继承/扩展

js_object


最后的小实战

function Foo(){
    getName = function(){
        console.log(1)
    }
    return this;
}
Foo.getName = function(){
    console.log(2)
}
Foo.prototype.getName = function(){
    console.log(3)
}
var getName = function(){
    console.log(4)
}
function getName(){
    console.log(5)
}
// pls ouput the results:
Foo.getName();
getName();
Foo().getName();
getName();
new Foo.getName();
new Foo().getName();
new new Foo().getName();

参考文献

  1. https://hackernoon.com/prototypes-in-javascript-5bba2990e04b

  2. https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Inheritance_and_the_prototype_chain

  3. https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/prototype

  4. https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/Objects/Object_prototypes

  5. https://github.com/mqyqingfeng/Blog/issues/2

  6. http://blog.vjeux.com/2011/javascript/how-prototypal-inheritance-really-works.html

  7. http://www.ruanyifeng.com/blog/2011/06/designing_ideas_of_inheritance_mechanism_in_javascript.html

  8. https://medium.com/@peterchang_82818/javascripter-%E5%BF%85%E9%A0%88%E7%9F%A5%E9%81%93%E7%9A%84%E7%B9%BC%E6%89%BF%E5%9B%A0%E5%AD%90-prototype-prototype-proto-object-class-inheritace-nodejs-%E7%89%A9%E4%BB%B6-%E7%B9%BC%E6%89%BF-54102240a8b4

  9. http://www.ituring.com.cn/article/56184

Hibop commented 5 years ago

image

原型prototype的修改与重写(覆盖)的区别

先创建对象,然后再修改原型:覆盖后,已经创建的对象无法访问到修改后的原型