首先需要知道的是

  • 模式只是思想。不要用 结构 看模式。
  • ES中函数是对象,因此函数也有属性和方法。
  • 每个函数含有两个属性: length 和 prototype
  • 每个函数含有两个非继承的方法: apply 和 call

属性中:最为重要的 prorotype 对应的值是一个 对象。 (length不重要,不管他了)

  • 就ES中的引用类型而言,prorotype 是他们所有实例方法的真正所在.
  • prototype 对象有一个 不可枚举的属性 constructor ,所以使用 for-in 无法发现。

todo

方法:每个函数包含两个非继承来的方法:call 和 apply

  • apply 接受两个参数。 函数名.apply(作用域, 参数[数组])

    举个例子。Math.max(param1, param2, ..)可以返回参数中的最大值。

    // 当我们想要对 数组使用,比较好的办法是使用 apply 方法
    const arr = [9, 91, 999];
    console.log(Math.max.apply(null, arr));
  • call 接受两个参数。 函数名.call(作用域, 参数a, 参数b, 参数...)

    function showColor() {
    console.log(this.color);
    return this.color;
    }
    function Test() {
    this.color = 'blue';
    }
    var o = {
    color: 'red'
    }
    var x = new Test();
    showColor.call(o); // red - 此时作用域为 o的作用域
    showColor.call(x); // blue - 此时作用域为 x的作用域

再多唠叨一下,为了以后我看自己写的傻叉东西能够看懂。- 说下 new 一个函数的过程

var x = new Person()

  • 1.创建一个空的 Object 对象。var obj = new Object
  • 2.将构造函数中的 this 指向刚创建的 obj对象。 (全局中直接调用/不使用new/函数this指向全局)
  • 3.将创建的 obj 的 __proto__ 指向构造函数 Person 的 prototype
  • 4.执行构造函数 Person 中的代码。

1. 工厂模式

工厂模式是 为了解决多个类似对象声明 的问题,为了解决实例化对象产生的重复。

  • 显示地创建了对象
  • 能够解决多个相似的问题
  • 无法识别对象
function createObj(name, age, sex) {
var obj = {};
obj.name = name;
obj.age = age;
obj.sex = sex;
obj.say = () => console.log('haha');
return obj;
} var x = createObj(1, 3, 4);
var y = createObj(9, 9, 8);
console.log(x.age, y.age); // 3, 9
x.age = 12;
x.say(); // haha
console.log('x.say and y.say:', x.say === y.say); // false
console.log(x, y);
console.log(typeof x); // object
console.log(x instanceof Object); // true

2. 构造模式

  • 每个方法都要在每个实例上重新实现一遍(重复创建)
  • 可以用来区分 实例的 类型
function Person(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
this.say = () => console.log('haha');
// this.constructor = 'test'; // 把这句注释打开,我们的 constructor 就改变了。
} var person1 = new Person(1, 3, 3);
var person2 = new Person(3, 9 ,0);
console.log(person1.say == person2.say); // false
person1.say(); console.log(person1.constructor === Person); // true
console.log(person1 instanceof Person); // true
console.log(person1 instanceof Object); // true 因为 所有对象都继承至 Object

var person1 = new Person(1, 3, 3) 它经历了什么?

  1. 创建了一个新对象
  2. 将构造函数的作用域 赋值给新对象 (因此 this 就指向了 该对象)
  3. 执行构造函数内部的代码(为这个对象执行新的属性)
  4. 返回新对象

上面若将注释打开,则利用constructor来判断将会出错,这就是 instanceof 的优点.

总有人想问,不使用 new 是什么样子的。

function Person(name, age) {
this.name = name;
this.age = age;
// return 1;
}
const a = new Person('hah', 'pig'); // Person {name: 'hah', age: 'pig'}
const b = Person(); // console.log(b) -> undefined -- 因为函数本身没有返回 -- return注释打开则不同了
const c = Person; // console.log(b) -> [Function: Person] -- 将函数的地址 给 c,c相当于是函数的别名了

3. 原型模式

每一个函数都有一个 prototype 属性,这个属性 是一个 指针,指向 调用构造函数而创建的那个对象实例的 原型对象。

  • 让所有对象共享他们的 属性 和 方法。
  • 缺点也是 所有的 属性和方法 都是 共享的。- 有些属性不需要共享。
  • 没有办法初始化参数。
function Person() {
} Person.prototype.name = 'chp';
Person.prototype.age = 8;
Person.prototype.say = function() {
console.log(this.name);
} var person1 = new Person();
var person2 = new Person(); person1.say();
console.log(person1.say === person2.say); // true
// 上面说明 没有多余地 创建 同样功能的函数,比起构造函数模式的优点。
这里还需要注意一下两种写法的不同。
function Cat() {}
Cat.prototype.name = 'test';
let cat = new Cat();
console.log(cat instanceof Cat); // true
console.log(cat.constructor === Cat);// true
// -----------------------------------------------
function Dog() {}
Dog.prototype = {
name: 'test'
}
let dog = new Dog();
console.log(dog instanceof Dog); // true
console.log(dog.constructor === Dog);// false [Function: Object]

[很关键]上面那样写实际上重写了原型的 constructor 为 Object

有必要的话,我们得加上constructor:Dog,另外,这样也证明了 我们还是用 instanceof 比较保险, constructor 会被修改。

function Person () {};
Person.prototype = {
constructor: 'Person'
};
console.log(Object.keys(Person.prototype)); // ['constructor'] - False,最好不被枚举出来
console.log(Person.prototype.constructor); // Person - Right function PersonX () {};
PersonX.prototype = {};
console.log(Object.keys(PersonX.prototype)); // []
console.log(PersonX.prototype.constructor); // [Function Object] - False,有必要最好是Person

另外以这种方式重设 constructor,会导致该属性变为可枚举的。

所以最完美的解决方式还要重设下 construcotr 的枚举属性

function Person () {};
Person.prototype = {
};
Object.defineProperty(Person.prototype, 'constructor', {
value: Person,
enumerable: false
});
console.log(Object.keys(Person.prototype)); // [] Right
console.log(Person.prototype.constructor); // [Function: Person]

关于 prototype 和 constructor

实例 f 的 constructor 指向了 构造函数 的 prototype 属性中的 constructor

function F(){};
console.log(F.prototype.constructor); // [Function: F]
const f = new F();
console.log(f.constructor); // [Function: F] console.log(f.constructor === F.prototype.constructor); // true

用实际程序来理解 高程三 中晦涩的原型对象。

  • 1.只要创建了一个函数,就会根据特定规则创建一个 prototype 属性,指向函数的原型对象。

    function F(){
    this.name = 'test';
    };
    console.log(F.prototype); // F{} => 函数F 的 prototype 属性指向了 原型对象 F{}
  • 2.默认情况下,所有原型对象的 prototype 属性中的 constructor 属性都会指向自身。

    function F(){
    this.name = 'test';
    };
    console.log(F.prototype); // F{}
    console.log(F); // [Function: F]
    console.log(F.prototype.constructor); // [Function: F]
  • 3.创建了自定义构造函数之后。其原型对象默认只会获得constructor属性。其他方法,都是从Object那里继承过来的。

    function F(){
    this.name = 'test';
    };
    console.log(F.prototype.constructor); // [Function: F]
    console.log(F.prototype.valueOf); // [Function: valueOf]
    console.log(F.prototype.x); // undefined
  • 为了理解上面一点,我们需要一点工具。 工欲善其事,必先利其器。

    更多关于工具的使用,请看最下方,这里为了不打断,只作简单介绍。

    • in 包含原型链-要求可访问(最多)
    • for-in 包含原型链-要求可访问、可枚举
    • Object.keys 只包含自身-要求可访问、可枚举(要求最高,所以最少)
    • Object.getPropertyNames 只包含自身-要求可访问(可访问到constructor)
  • 下面就来证实上面这一点:valueOf 来自于 原型链 而不是我们自定义的构造函数。

    function F(){};
    console.log(Object.getOwnPropertyNames(F.prototype)); // 只有一个: constructor
    console.log(Object.getOwnPropertyNames(Object)); // ['length', 'name', 'argument', 'caller', 'create', 'prototype', ... ]
    console.log(Object.getOwnPropertyNames(Object.prototype)); // ['hasOwnProperty', 'constructor', 'toString', 'valueOf', '__proto__', ...]
  • 4.当调用构造函数创建一个新实例后,该实例的内部将包含一个指针__proto__(内部属性) ,指向构造函数的原型对象

    • 1.这个属性在 Node v6.10.0 上不可见,在很多实现上都不可见。但在Chorme高版本可见。

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

      • a.Chrome浏览器上我们可以看到 __proto__ 的实现

      • b.同样的代码写在 Node.js 中的效果

      • c.实例.__proto__构造函数.prototype

      • d.验证他们相等

  • 5.F的每个实例,都包含一个__proto__。实例本身虽然和构造函数没有直接的关系。但是和构造函数的原型却有直接的关系。虽然这两个实例都不包含 属性 和 方法。但我们却可以通过原型链查找来访问。

    F本身没有 valueOf 方法,但是可以通过 原型链查找,查找到 Object.prototype 里的方法.

    function F(){};
    const f = new F();
    console.log(f.valueOf()); // F {}
  • 6.另外 JS给我提供了 实例.__proto__ 是否指向 构造函数.ptototype(这个就是原型对象啦

    1. JS Object.prototype.isPrototypeOf()
    2. ES5 也新增了一个方法 Object.getPrototypeOf()
    function F() {};
    const f1 = new F();
    const f2 = new F();
    console.log(F.prototype.isPrototypeOf(f1)); // true
    console.log(F.prototype.isPrototypeOf(f2)); // true
    console.log(Object.getPrototypeOf(f1)) // F {}
    console.log(Object.getPrototypeOf(f1) === F.prototype); // true
  • 7.原型链的搜索过程

    1. 实例 f 有 girlFriend 属性吗? 是的,没有
    2. 实例 f 的 原型(prototype) 有 girlFriend 属性吗? 有,上交给国家!

      这样就找到了。
  • 8.如果在 实例 中添加和 原型 中有的属性,实例中的属性 会覆盖 原型中的属性。

    function F() {};
    F.prototype.name = '123';
    let f1 = new F();
    let f2 = new F();
    f1.name = 'xx';
    console.log(f1.name); // xx
    console.log(f2.name); // 123
    console.log(f1.hasOwnProperty("name")); // true
    delete(f1.name);
    console.log(f1.name); // 123
    console.log(f1.hasOwnProperty("name")); // false
  • 9.原型的动态性。我们对 原型对象 所做的任何修改都能立刻体现在 实例上。

    但是要注意,重写原型 和 给原型添加属性 是不一样的。

    function F(){};
    let f = new F();
    F.prototype.name = '123';
    console.log(f.__proto__.name); // 123
    console.log(f.name); // 123

    重写原型之后 切断了 原型与之前任何已经存在的 实例之间的 联系。他们引用的仍是最初的原型

    function F(){};
    let f = new F();
    F.prototype.name = 'ccc';
    F.prototype = {
    age: '20'
    }
    console.log(f.__proto__.name); // ccc
    console.log(f.name); // ccc
    console.log(f.__proto__.age); // undefined
    console.log(f.age); // undefined
  • 10.关于上面提到的另外一种 原型的写法, 我们可以 强行让 constructor 正确。但是这样写有一个缺点,那就是 constructor 变为 可枚举的类型了。因此,我们常常使用 ES5 的Object.defineProperty方法 来优化。

    function F() {};
    F.prototype = {
    constructor: 'F',
    name: '123'
    }
    const f = new F();
    console.log(f.constructor); // F

    再复习一次。

    function F() {};
    F.prototype = {
    name: '123'
    }
    Object.defineProperty(F.prototype, "constructor", {
    enumerable: false,
    value: F
    });
    const f = new F();
    console.log(Object.keys(F.prototype)); // ['name']

由于属性方法 共享的 缘故, 原型的 主要缺点在于 实例的 独特性受到了限制。

回忆一下

  • 缺点也是 所有的 属性和方法 都是 共享的。- 有些属性不需要共享。
  • 没有办法初始化参数。- 没有传参的地方

4. 组合构造模式 : 集 构造函数模式 和 原型模式 优点于一身。·

需要共享的属性和函数使用原型声明。不需要共享的和参数,都使用 构造函数来声明。

function Pet(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
}
Pet.prototype.say = function() {
console.log(this.age);
} var cat = new Pet('cat', 3, '1');
var dog = new Pet('dog', 4, '3');
console.log(cat.say === dog.say); // true
cat.say();

5. 动态原型模式 - 其实就是对组合构造模式的一点点优化而已。

组合组合,明明一个类型,却要进行两个操作。动态原型模式 = 封装的 组合构造模式。

function F(name) {
this.name = name;
if (typeof this.sayName != 'function') {
F.prototype.sayName = () => console.log(this.name);
F.prototype.sayHi = () => console.log('hi');
}
}
let f = new F('hah');
let x = new F('12');
console.log(x.sayName == f.sayName); // true

其中判断的作用主要是,只在 第一次 实例化前 进行相关 prototype 的设置。只设置一次。

6. 寄生构造函数模式 - 其实就是工厂模式。硬要名字而已,无需特别在意。区别是使用了new。区别本身在于作用域,与模式无关。

function F() {
var o = new Object();
console.log(this);
o.name = '123';
return o;
}
var a = new F(); // F{}
var b = F(); // 视环境而定,若环境为 Node 则为 Windows
console.log(b.__proto__ == F.prototype); // false,跟工厂模式一样,返回的是内部的 Object

该模式常用来 创造 基本引用类型 的 扩展。因为直接扩展到全局不太好。

function optionArray() {
var o = new Array();
o.push.apply(o, arguments);
o.toShow = function() {
return this.join('/');
}
return o;
}
var x = new optionArray('little prince', 'red hat');
console.log(x);
console.log(x.toShow());

其实,我们完全可以用其他的方法 创造一个类型。 而不是用它

7. 稳妥构造函数,其实可以理解成 用 闭包 实现 C++ 的私有变量 和 公有方法

  • 没有公共属性(私有属性,公有方法---这就是C++)
  • 方法不引用 this 对象
  • 和寄生构造函数很像,区别在于 不使用 new
function F(name) {
var o = new Object();
o.sayName = () => console.log(name);
return o;
}
let f = F('nio');
f.sayName(); // nio

目的在于,除了 sayName() 函数自己,没有人能够找到 name.

思想借鉴与 C++ 中的 private 和 public


后记-复习-理解原型对象

Person.protype 指向了 原型对象。

原型对象 Person.protype.constructor 又指回了 Person

console.log(Person.prototype.constructor); [function:Person]

console.log(Person.prototype); // Person {name: 'chp', age: 8, say:[Function]}

hasOwnProperty 和 in 和 for-in 和 Object.keys()

function F() {
this.name = 'csn'; // 构造函数中
}
F.prototype.sex = 'female'; // 原型中
let f = new F();
f.occupation = 'student'; // 实例中
/* --------------------------------------------- */
console.log(f.hasOwnProperty("occupation")); // true, 自身实例有。
console.log(f.hasOwnProperty("sex")); // false, 在原型中
console.log(f.hasOwnProperty("name")); // true, 构造函数属于自身。
console.log(F.prototype.hasOwnProperty("constructor")); // true (包含不可枚举的)
/* --------------------------------------------- */
console.log("occupation" in f); // true
console.log("sex" in f); // true
console.log("name" in f); // true
console.log("constructor" in f); // true
/* --------------------------------------------- */
for(let x in f) { console.log(x); } // name, occupation, sex (包含原型链,不包含不可枚举的)
for(let x in F.prototype) {console.log(x);} // sex
/* --------------------------------------------- */
console.log(Object.keys(f)); // ['name', 'occupation'] (不包含原型链,不包含不可枚举的)
console.log(Object.keys(F.prototype)); // ['sex']

in 搜索包括原型链上的 所有能访问(不管能不能枚举)的类型。而 hasOwnProperty 只是搜索本身的。

person1.own = 'haha';
console.log("own" in person1); // true
console.log("age" in person1); // true
console.log(person1.hasOwnProperty("own")); // true
console.log(person1.hasOwnProperty("age")); // false

in 和 for-in 的差别

Object.defineProperty(person1, "own", {
enumerable: false
});
console.log("own" in person1); // true
for(var property in person1) {
console.log(property); // name,age,say
// (已然没有了 own,它被设为了不可枚举)
}

Object.keys 和 Object.getOwnPropertyNames()

  • Object.keys 返回 本身所有可枚举的对象
  • Object.getOwnPropertyNames 返回 所有的对象,包括不可枚举的

js里所有的函数都可以做构造函数,并且每个函数都有一个 prototype 属性,这个属性对应的值是一个对象,这个对象包含唯一 不可枚举的属性 constructor

var person1 = new Person();
person1.own = 1;
console.log(Object.keys(person1)); // ['own']
console.log(Object.keys(Person.prototype)); // ['name', 'age', 'say']
console.log(Object.getOwnPropertyNames(person1)); // ['own']
console.log(Object.getOwnPropertyNames(Person.prototype));
// ['constructor', 'name', 'age', 'say']

综上所述,每个玩意儿的要求不同

方法名 特点 评价
in 原型链和自身-所有 天罗地网,存在查找
for-in 原型链和自身-可枚举 要求可枚举
Object.keys 仅自身-可枚举 精确查找
Object.getPropertyNames 仅自身-可枚举 仅自身的所有
hasOwnProperty 仅自身-所有 同上
  • 属性不可枚举也能用的: in/hasOwnProperty.
  • 属性自身和原型链都能用的:in/for-in

关于工厂模式、寄生构造模式、稳妥构造函数的区别

  • 稳妥构造函数: 不使用o.name = name这样的形式,拒绝被外部访问。用闭包。不使用 this/new 。拒绝属性直接访问。拒绝属性直接访问。拒绝属性直接访问。

这是一种思想。

  • 寄生构造模式:目的是为了扩展一种新类型,又不影响全局的使用。使用 new。
  • 工厂模式。稳妥构造函数的 反面,不用 new。

这里只写 工厂模式 和 稳妥构造函数 的 示例。

function F1(name) {
let o = new Object();
o.name = name;
return o;
}
function F2(name) {
let o = new Object();
o.getName = () => name;
return o;
}
let f1 = F1('factory');
let f2 = F2('safe');
// 要想获取名字的话
console.log(f1.name); // factory
console.log(f2.name); // undefined
console.log(f2.getName()); // safe

complete.

JS模式和原型精解的更多相关文章

  1. 《React Native 精解与实战》书籍连载「Node.js 简介与 React Native 开发环境配置」

    此文是我的出版书籍<React Native 精解与实战>连载分享,此书由机械工业出版社出版,书中详解了 React Native 框架底层原理.React Native 组件布局.组件与 ...

  2. js面向对象、创建对象的工厂模式、构造函数模式、原型链模式

    JS面向对象编程(转载) 什么是面向对象编程(OOP)?用对象的思想去写代码,就是面向对象编程. 面向对象编程的特点 抽象:抓住核心问题 封装:只能通过对象来访问方法 继承:从已有对象上继承出新的对象 ...

  3. js设计模式:工厂模式、构造函数模式、原型模式、混合模式

    一.js面向对象程序 var o1 = new Object();     o1.name = "宾宾";     o1.sex = "男";     o1.a ...

  4. JS面向对象基础讲解(工厂模式、构造函数模式、原型模式、混合模式、动态原型模式)

    什么是面向对象?面向对象是一种思想. 面向对象可以把程序中的关键模块都视为对象, 而模块拥有属性及方法. 这样如果我们把一些属性及方法封装起来,日后使用将非常方便,也可以避免繁琐重复的工作.   工厂 ...

  5. JS面向对象(1)——构造函数模式和原型模式

    1.构造函数模式 构造函数用来创建特定的类型的对象.如下所示: function Person(name,age,job){ this.name=name; this.job=job; this.ag ...

  6. 【js基础】创建对象的几种常见模式(工厂模式,构造函数模式,原型模式,构造原型组合模式)

    一.工厂模式 缺点:没有解决对象识别的问题 优点:解决了创建多个相似对象的问题 function createPerson(name,age,job){ var o = new Object(); o ...

  7. JS中使用组合构造函数模式和原型模式

    创建自定义类型的最常见方式,就是组合使用构造函数模式与原型模式.构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性. 结果,每个实例都会有自己的一份实例属性的副本,但同时又共享着对方法的 ...

  8. 《React Native 精解与实战》书籍连载「React Native 底层原理」

    此文是我的出版书籍<React Native 精解与实战>连载分享,此书由机械工业出版社出版,书中详解了 React Native 框架底层原理.React Native 组件布局.组件与 ...

  9. [javascript]模块化&命名污染—from 编程精解

    最近看了编程精解里面的模块化一章,很受启发. /****************/ 在开发的实际过程中,根据页面或者逻辑布局,js代码可以按照功能划分为若干个区块:数据交互.表单验证.页面布局等等模块 ...

随机推荐

  1. 11-02SQLserver基础--字符串函数

    数据库の函数 一.内置函数--字符串函数 1.--ASCII 返回字符串的首字母的ASCII编码 select ASCII('ame') select ASCII(xingming)from xues ...

  2. oracle错误-ORA-12519, TNS:no appropriate service handler found

    转自:https://blog.csdn.net/u013147600/article/details/48380841

  3. mysql 打印随机数

    select rand(); 这样取出来的数据是类似这样的: 0.5389902438400223 要几位自己取几位: 取得方法类似 select substr(concat("000000 ...

  4. [Python Study Notes]水平柱状图绘制

    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ...

  5. taglib标签在web.xml文件中报错的解决办法

    报错的原因分析: 在jsp2.0中,且2.4版的DTD验证中,taglib描述符,正确写法是放到<jsp-config></jsp-config>描述符中.所以,我们的tagl ...

  6. Lucene打分公式的数学推导

    原文出自:http://www.cnblogs.com/forfuture1978/archive/2010/03/07/1680007.html 在进行Lucene的搜索过程解析之前,有必要单独的一 ...

  7. iframe自适应高度(转)

    iframe自适应高度 (2013-04-23 17:29:49) 标签: iframe 高度 自适应 js 杂谈 分类: 网页制作 有时候我们的网站需要引入其他网站的东西,比如评论,这时候就需要使用 ...

  8. 算法Sedgewick第四版-第1章基础-005一封装输入(可以文件,jar包里的文件或网址)

    1. package algorithms.util; /*********************************************************************** ...

  9. conda 添加bioconda源,创建/删除/重命名环境

    1.conda安装 在https://repo.continuum.io/miniconda/选择conda版本 wget "https://repo.continuum.io/archiv ...

  10. GCD学习(六) dispatch_async 和dispatch_sync

    dispatch_sync(),同步添加操作.他是等待添加进队列里面的操作完成之后再继续执行. dispatch_queue_t concurrentQueue = dispatch_queue_cr ...