this和对象原型正则表达式
第一章 关于thischrome
1.1 为何要用this设计模式
this 提供了一种更优雅的方式来隐式“传递”一个对象引用,所以能够将 API 设计 得更加简洁而且易于复用。显式传递上下文对象会让代码变得愈来愈混乱,使用 this 则不会这样。数组
1.2 误解浏览器
1.2.1 指向自身安全
除了函数对象,还有更多更适合存储状态的地方。 函数表达式须要引用自身时(好比使用递归,或者定义本身的属性和方法),应当要给函数表达式具名,而不是使用arguments.callee(已经被弃用和批判的用法)。app
function foo(num) { console.log( "foo: " + num ); this.count++; // 记录 foo 被调用的次数 // 记录 foo 被调用的次数改成 // foo.count++;,能够获得正确答案4,但回避了this问题 } foo.count = 0; var i; for (i=0; i<10; i++) { if (i > 5) { foo( i ); // 使用 call(..) 能够确保 this 指向函数对象 foo 自己 // foo.call( foo, i ); } } // foo: 6 // foo: 7 // foo: 8 // foo: 9 // foo 被调用了多少次? console.log( foo.count ); // 0 -- WTF? *执行 foo.count = 0 时,的确向函数对象 foo 添加了一个属性 count。可是函数内部代码 this.count 中的 this 并非指向那个函数对象,因此虽然属性名相同,根对象却并不相 同,困惑随之产生。
若是要从函数对象内部引用它自身,那只使用 this 是不够的。通常来讲你须要经过一个指 向函数对象的词法标识符(变量)来引用它。dom
1.2.2 它的做用域异步
this在任何状况下都不指向函数的词法做用域。ide
做用域“对象”没法经过 JavaScript 代码访问,它存在于 JavaScript 引擎内部。
1.3 this究竟是什么
this是在运行时绑定的,而不是在编写时绑定,它的上下文取决于函数调用时的各类条件。this 的绑定和函数声明的位置没有任何关系,this只取决于函数的调用方式。
当一个函数被调用时,会建立一个活动记录(有时候也称为执行上下文)。这个记录会包 含函数在哪里被调用(调用栈)、函数的调用方法、传入的参数等信息。this 就是记录的 其中一个属性,会在函数执行的过程当中用到。
第二章 this全面解析
2.1 调用位置
调用位置是函数在代码中被调用的位置,最重要的是要分析调用栈。 好比
function baz(){ // 当前调用栈是baz,调用位置是全局做用域 console.log(this); // window || global bar(); } function bar(){ // 当前调用栈是baz -> bar,调用位置在baz中 // 能够拿到baz词法做用域的变量,可是和this无关 console.log(this); // window || global foo(); } function foo(){ // 当前调用栈是baz -> bar -> foo,调用位置在bar中 // 能够拿到bar和baz词法做用域的变量,可是和this无关 console.log(this) // window || global } baz(); // baz的调用位置
2.2 绑定规则
2.2.1 默认绑定
this默认指向全局对象。 严格模式下,若是this找不到具体对象,就是undefined。 但若是函数声明的区域不是严格模式的,即便在严格模式下调用,也不影响它将默认的this指向全局。
function foo() { console.log( this.a ); } var a = 2; foo(); // 2 //当调用 foo() 时,this.a 被解析成了全局变量 a。函数调用时应用了 this 的默认绑定,所以 this 指向全局对象。 //。在代码中,foo() 是直接使用不带任何修饰的函数引用进行调用的,所以只能使用 默认绑定,没法应用其余规则。 //若是使用严格模式(strict mode),那么全局对象将没法使用默认绑定 //此 this 会绑定 到 undefined: function foo() { "use strict"; console.log( this.a ); } var a = 2; foo(); // TypeError: this is undefined
2.2.2 隐式绑定
this隐式绑定的,是整个调用链上,处于方法上一层的对象。
function foo(){ console.log(this.a) } var obj = { a: 2, foo: foo, }; obj.foo(); //2 //其实等效于,JS的函数调用其实更像是一种语法糖 obj.foo.call(obj); //当函数引 用有上下文对象时,隐式绑定规则会把函数调用中的 this 绑定到这个上下文对象。由于调 用 foo() 时 this 被绑定到 obj,所以 this.a 和 obj.a 是同样的。
隐式丢失
须要特别注意的是,回调函数丢失this绑定是很是常见的
function foo() { console.log( this.a ); } function doFoo(fn) { // fn 其实引用的是 foo fn(); // <-- 调用位置! } var obj = { a: 2, foo: foo }; var a = "oops, global"; // a 是全局对象的属性 doFoo( obj.foo ); // "oops, global" //参数传递其实就是一种隐式赋值,所以咱们传入函数时也会被隐式赋值
由于调用它的操做一般不包含隐式绑定和显式绑定。 这个时候能够选择:
2.2.3 显式绑定
call和apply的效果同样,传入参数的方式不同。 call从第二个参数开始,逐个传入。 apply的第二个参数是一个Array,参数放在Array里。 bind的参数形式相似于call,可是返回的是显式绑定后的函数对象,须要后续去调用执行。 bind经常使用于柯里化一个函数对象。
硬绑定
建立了函数 bar(),并在它的内部手动调用 了 foo.call(obj),所以强制把 foo 的 this 绑定到了 obj。不管以后如何调用函数 bar,它 总会手动在 obj 上调用 foo。这种绑定是一种显式的强制绑定,所以咱们称之为硬绑定。
【硬绑定的典型应用场景】就是建立一个包裹函数,传入全部的参数并返回接收到的全部值;另外一种使用方法是建立一个 i 能够重复使用的辅助函数
function foo(something) { console.log( this.a, something ); return this.a + something; } var obj = { a:2 }; var bar = function() { return foo.apply( obj, arguments ); }; var b = bar( 3 ); // 2 3 console.log( b ); // 5 // ES6写法 Function.prototype._bind = function(thisArg, ...args){ return (...newArgs) => this.call(thisArg, ...args, ...newArgs); } // ES5写法 Function.prototype._bind = function(thisArg){ var args = [].slice.call(arguments, 1); var fn = this; return function(){ var newArgs = [].slice.call(arguments, 0); return fn.apply(thisArg, args.concat(newArgs)); }; } //MDN提供的polyfill if(!Function.prototype.bind) { Function.prototype.bind = function(oThis) { // 类型判断 if(typeof this !== 'function'){ throw new TypeError('Function.prototype.bind - what is trying to be found is not callable'); } var aArgs = Array.prototype.slice.call(arguments, 1), fToBind = this, fNOP = function(){}, fBound = function(){ return fToBind.apply(( this instanceof fNOP && oThis? this: oThis ), aArgs.concat(Array.prototype.slice.call(arguments)) ); }; // 继承原型链 fNOP.prototype = this.prototype; fBound.prototype = new fNOP(); return fBound; } }
2.2.4 new绑定
实际上并不存在所谓的“构造函数”,只有对于函数的“构造调用”。
用new来调用函数,会自动执行下面的操做:
若是函数没有返回其余对象,那么new表达式中的函数调用会自动返回这个新对象。
function foo(a) {
this.a = a;
}
var bar = new foo(2);
console.log( bar.a ); // 2
//new 来调用 foo(..) 时,咱们会构造一个新对象并把它绑定到 foo(..) 调用中的 this 上
2.3 优先级
2.4 绑定例外
2.4.1 被忽略的this (空对象)
显式绑定中,若是对this不敏感,能够传入null,可是可能有反作用。 更安全的作法是,传入一个空对象,即var empty = Object.create(null),它连指向Object.prototype的proto都没有,比{}更空。
function foo(a,b) { console.log( "a:" + a + ", b:" + b ); } // 咱们的 DMZ 空对象 var ø = Object.create( null ); // 把数组展开成参数 foo.apply( ø, [2, 3] ); // a:2, b:3 // 使用 bind(..) 进行柯里化 var bar = foo.bind( ø, 2 ); bar( 3 ); // a:2, b:3 //使用变量名 ø 不只让函数变得更加“安全”,并且能够提升代码的可读性,由于 ø 表示 “我但愿 this 是空”,这比 null 的含义更清楚。
2.4.2 间接引用
(p.foo = o.foo)()这里返回的引用是foo,而不是p.foo,严格模式下,它的this指向会指向undefined。
2.4.3 软绑定
硬绑定会大大下降函数的灵活性,使 用硬绑定以后就没法使用隐式绑定或者显式绑定来修改 this。
软绑定,能够实现和硬绑定相 同的效果,同时保留隐式绑定或者显式绑定修改 this 的能力。
if (!Function.prototype.softBind) { Function.prototype.softBind = function(obj) { var fn = this; // 捕获全部 curried 参数 var curried = [].slice.call( arguments, 1 ); var bound = function() { return fn.apply( (!this || this === (window || global)) ? obj : this curried.concat.apply( curried, arguments ) ); }; bound.prototype = Object.create( fn.prototype ); return bound; }; }
2.5 this词法
lambda表达式(箭头函数)的this是根据外层做用域来决定。 lambda表达式经常使用于回调函数,好比eventListener和setTimeout操做中。
function foo() { // 返回一个箭头函数 return (a) => { // 这里的this实际上是foo中的this console.log(this.a); }; } var obj1 = {a:2}, obj2 = {a:3}; // bar的this指向foo的this,foo的this此时显式绑定了obj1 var bar = foo.call(obj1); // 虽然显式绑定了bar的this,可是这种方法对箭头函数不起做用 // 它的this依然指向先前foo被显式绑定的obj1 bar.call(obj2); //2
箭头函数最经常使用于回调函数中,例如事件处理器或者定时器
ES6 中的箭头函数并不会使用四条标准的绑定规则,而是根据当前的词法做用域来决定 this,具体来讲,箭头函数会继承外层函数调用的 this 绑定(不管 this 绑定到什么)。这 其实和 ES6 以前代码中的 self = this 机制同样。
第三章 对象
3.1 语法
//文字语法(声明形式)(建立简单对象时推荐使用) var myObj = { key: value, }; //构造形式 var myObj = new Object(); myObj.key = value;
3.2 类型
JS一共有七种主要类型(语言类型):
注意,简单基本类型(string、boolean、number、null 和 undefined)自己并非对象。 null 有时会被看成一种对象类型,可是这其实只是语言自己的一个 bug,即对 null 执行 typeof null 时会返回字符串 "object"。1 实际上,null 自己是基本类型。
❤ 注 1: 原理是这样的,不一样的对象在底层都表示为二进制,在 JavaScript 中二进制前三位都为 0 的话会被判 断为 object 类型, null 的二进制表示是全 0,天然前三位也是 0,因此执行 typeof 时会返回“object”。
console.log(typeof null); //object console.log(typeof string); //undefined console.log(typeof number); //undefined console.log(typeof String); //function console.log(typeof Number);//function
内置对象(注意第一个字母大写,这些都是构造函数):
在 JavaScript 中,它们实际上只是一些内置函数。这些内置函数能够看成构造函数 (由 new 产生的函数调用——参见第 2 章)来使用,从而能够构造一个对应子类型的新对 象。
补充:js中的基本类型和引用类型
var strPrimitive = "I am a string"; typeof strPrimitive; // "string" strPrimitive instanceof String; // false //typeof:肯定变量是字符串、数值、布尔值仍是undefined的最佳工具。 //instanceof :判断是不是某个对象类型。 var strObject = new String( "I am a string" ); typeof strObject; // "object" strObject instanceof String; // true // 检查 sub-type 对象 Object.prototype.toString.call( strObject ); // [object String]
3.3 内容
3.3.1 可计算属性名
ES6支持在键访问中传入一个表达式来看成属性名,好比myObj[prefix + 'foo']。在文字形式中使用 [] 包裹一个表达式来看成属性名
var prefix = "foo"; var myObject = { [prefix + "bar"]:"hello", }; myObject["foobar"]; // hello
3.3.2 属性与方法
属于对象的函数一般称为方法。
即便你在对象的文字形式中声明一个函数表达式,这个函数也不会“属于”这个对象—— 它们只是对于相同函数对象的多个引用
3.3.3 数组
数组经过数字下标[索引]访问。 数组能够添加命名属性,可是不会改变其length值。
var myArray = [ "foo", 42, "bar" ]; myArray.baz = "baz"; myArray.length; // 3 myArray.baz; // "baz"
注意:若是你试图向数组添加一个属性,可是属性名“看起来”像一个数字,那它会变成 一个数值下标(所以会修改数组的内容而不是添加一个属性):
var myArray = [ "foo", 42, "bar" ]; myArray["3"] = "baz"; myArray.length; // 4 myArray[3]; // "baz"
3.3.4 复制对象
ES6中可使用Object.assign(目标对象,源对象)方法(浅复制)
因为 Object.assign(..) 就是使用 = 操做符来赋值,所 以源对象属性的一些特性(好比 writable)不会被复制到目标对象。
const cloned = Object.assign(
// 生成原型链
Object.create(Object.getPrototypeOf(target)),
target
);
须要注意的是,PropertyDescriptor不会被按原样复制,而是保持默认值。
3.3.5 属性描述符
从 ES5 开始,全部的属性都具有了属性描述符。
var myObject = { a:2 }; Object.getOwnPropertyDescriptor( myObject, "a" ); { value: 2, writable: true, //writable(可写) enumerable: true, //enumerable(可枚举) configurable: true // configurable(可配置) } //也可使用 Object.defineProperty(..) 来添加一个新属性或者修改一个已有属性(若是它是 configurable)并对特性进行设置 var myObject = {}; Object.defineProperty( myObject, "a", { value: 2, writable: true, configurable: true, enumerable: true } ); //只要属性是可配置的,就可使用 defineProperty(..)
一个descriptor有6个可能的属性,同时只能拥有其中4个(分为基本类型和引用类型):
3.3.6 不变性
结合writable: false和configurable: false就能够建立一个真正的常量属性。
var myObject = {};
Object.defineProperty( myObject, 'Favorite_Number', {
value: 22,
writable: false,
configurable: false,
});
冻结,使用Object.freeze( targetObject ),在seal基础上在将属性改成writable: false,只读。(“深度冻结”一个对象,具体方法为,首先在这个对象上调用 Object.freeze(..), 而后遍历它引用的全部对象并在这些对象上调用 Object.freeze(..)。)
3.3.7 [[Get]]
3.3.8 [[Put]]
给对象的属性赋值会触发 [[Put]] 来设置或者建立这个属性(实际状况复杂)。
若是属性已经存在,Put会检查:
3.3.9 Getter和Setter
在 ES5 中可使用 getter 和 setter 部分改写默认操做,可是只能应用在单个属性上,没法 应用在整个对象上。
getter 是一个隐藏函数,会在获取属性值时调用。setter 也是一个隐藏 函数,会在设置属性值时调用。
当一个属性定义 getter、setter 或者二者都有时,这个属性会被定义为“访问描述 符”(和“数据描述符”相对)。对于访问描述符来讲,JavaScript 会忽略它们的 value 和 writable 特性,取而代之的是关心 set 和 get(还有 configurable 和 enumerable)特性。
定义方式
// 直接式 var myObject = { // 给a定义一个getter,此时再去访问a,只会得到2 get a(){ return 2; }, // 不是单纯的setter,而会取两倍的值 set a(val){ this._a_ = val * 2; } //即使有合法的 setter,因为咱们自定义的 getter 只会返回 2,因此 set 操做是 没有意义的。 } // descriptor式 Object.defineProperty(myObject, 'b', { get: function(){ return this._b_; //名称 _b_ 只是一种惯例,没有任何特殊的行为——和其余普通属性 同样。 }, set: function(val){ this._b_ = val*2; }, enumerable: true,//确保b会出如今对象的属性列表中 }); myObject.b = 2 console.log(myObject.b)//4
3.3.10 存在性(如何区分undefined)
("a" in myObject); // true
("b" in myObject); // false
myObject.hasOwnProperty( "a" ); // true
myObject.hasOwnProperty( "b" ); // false
hasOwnProperty只会检查属性是否在对象中,不会检查原型链。
3.3.11 可枚举性
可枚举,至关于能够出如今对象属性的遍历中。 propertyIsEnumerable会检查给定的属性名是否直接存在于对象中(而不是原型链中),而且知足enumerable: true。
var myObject = { }; Object.defineProperty( myObject, "a", // 让 a 像普通属性同样能够枚举 { enumerable: true, value: 2 } ); Object.defineProperty( myObject, "b", // 让 b 不可枚举 { enumerable: false, value: 3 } ); myObject.b; // 3 ("b" in myObject); // true myObject.hasOwnProperty( "b" ); // true //in 和 hasOwnProperty(..) 的区别在因而否查找 [[Prototype]] 链 for (var k in myObject) { console.log( k, myObject[k] ); } // "a" 2 //myObject.b 确实存在而且有访问值,可是却不会出如今 for..in 循环中 myObject.propertyIsEnumerable( "a" ); // true myObject.propertyIsEnumerable( "b" ); // false Object.keys( myObject ); // ["a"] //Object.keys(..) 会返回一个数组,包含全部可枚举属性 Object.getOwnPropertyNames( myObject ); // ["a", "b"] //Object.getOwnPropertyNames(..) 会返回一个数组,包含全部属性,不管它们是否可枚举。
3.4 遍历
使用Symbol.iterator对象,能够用来定义对象的@@iterator内部属性:
var myArray = [ 1, 2, 3 ]; var it = myArray[Symbol.iterator](); it.next(); // { value:1, done:false } it.next(); // { value:2, done:false } it.next(); // { value:3, done:false } it.next(); // { done:true },这个机制和 ES6 中发生器函数的语义相 关
和数组不一样,普通的对象没有内置的 @@iterator,因此没法自动完成 for..of 遍历。简单来讲,这样作是为了不影响将来的对象 类型。,咱们能够给须要遍历的对象定义@@iterator
var myObject = { a: 2, b: 3, }; Object.defineProperty(myObject, Symbol.iterator, { enumerable: false, writable: false, configurable: true, value: function() { var o = this; var idx = 0; var ks = Object.keys(o); return { next: function() { return { value: o[ks[idx++]], done: (idx > ks.length), }; }, }; } }); var it = myObject[Symbol.iterator](); it.next(); // {value:2, done: false} it.next(); // {value:3, done: false} it.next(); // {value:undefined, done: true}
能够用for of 定义一个无限迭代器,如产生随机数等
var randoms = { [Symbol.iterator]: function() { return { next: function() { return { value: Math.random() }; } }; } }; var randoms_pool = []; for (var n of randoms) { randoms_pool.push( n ); // 防止无限运行! if (randoms_pool.length === 100) break; }
第四章 混合对象“类”
面向类的设计模式:实例化(instantiation),继承(inheritance),(相对)多态(polymorphism)。
4.1 类理论
类理论包括:
4.4.1 类设计模式
面向对象的设计模式有,迭代器模式、观察者模式、工厂模式、单例模式等。
4.4.2 JavaScript中的类
JS中的类是构造函数与原型的结合,与其余语言的类不一样。
4.2 类的机制
4.2.1 建造
为了得到真正能够交互的对象,咱们必须按照类来建造一个东西,这个东西一般被称为实例。有须要的话,咱们能够直接在实例上调用方法并访问其全部公有数据属性。
4.2.2 构造函数
类实例是由一个特殊的类方法构造的,这个方法名和类名相同,被称为构造函数。这个方法的任务是初始化实例所须要的全部信息及属性。
function CoolGuy() { this.name = 'John'; }
构造函数须要用new来调用,这样引擎才会去构造一个新的类实例。
4.3 类的继承
子类须要继承父类的属性与原型方法。
4.3.1 多态
子类能够重写父类方法。 子类重写的方法在子类的prototype上,不会改变父类的prototype,只是子类在寻找方法时,会跟随原型链,率先找处处于自身原型上的方法,从而调用。 ES6用super代指父类的构造函数,从而能让子类经过这种方式找到父类的原型及其方法。
4.3.2 多重继承
JS的继承是单向的。 JS不支持多重继承,所以使用混入模式实现多重继承。
4.4 混入
JavaScript 中只有对象,并不存在能够被实例化的“类”。一个对象并不会被复制到其余对 象,它们会被关联起来。JavaScript 开发者也想出了一个方法来 模拟类的复制行为,这个方法就是混入。
4.4.1 显式混入
JavaScript 中的函数没法(用标准、可靠的方法)真正地复制,因此你只能复制对共享 函数对象的引用(函数就是对象;参见第 3 章)。若是你修改了共享的函数对象(好比 ignition()),好比添加了一个属性,那 Vehicle 和 Car 都会受到影响
// 不覆盖属性 function mixinWithoutOverwrite(source, target) { for (var key in source){ // 只在不存在的状况下复制 if( !(key in target) ){ target[key] = source[key]; } } return target; } //覆盖属性 //若是咱们是先进行复制而后对 Car 进行特殊化的话,就能够跳过存在性检查。不过这种方 法并很差用而且效率更低,因此不如第一种方法经常使用: function mixinWithOverwrite(source, target) { for (var key in source){ target[key] = source[key]; } return target; } //。从技术角度来讲,函数实际上没有 被复制,复制的是函数引用(对象数组)。
另外一种变体 — 是寄生式继承,将父类的实例混入子类。它既是显式的又是隐式的
function Super() { this.name = 'father'; } Super.prototype.say = function() { console.log('Method from father'); } function Sub() { // 父类实例 var father = new Super(); father.name = 'son'; // 保存引用 var extendedSay = father.say; // 多态重写 father.say = function() { extendedSay.call(this); console.log('Method from son'); } return father; } // 调用时无需用new。是由于咱们没有使用这个对象而是返回了咱们本身的 car 对象,所 以最初被建立的这个对象会被丢弃,所以能够不使用 new 关键字调用 Car(),能够避免建立并丢弃多余的对象 var son = Sub();
4.4.2隐式混入
隐式混入其实就是把其余对象的方法拿过来使用,使用call、apply、或bind,将其this指针指向自身。(尽可能避免使用这样 的结构,以保证代码的整洁和可维护性。)
var landLord = { name: 'himself', checkMoney: function() { console.log('Landlord is giving money to ' + this.name); }, } var robinHood = { name: 'Robin Hood', rob: function() { // 把其余对象的方法拿过来使用 landLord.checkMoney.call(this); }, } robinHood.rob(); //Landlord is giving money to Robin Hood
第五章 原型
5.1 [[Prototype]]
几乎全部的对象在建立时 [[Prototype]] 属性都会被赋予一个非空的值。
对于默认的Get操做来讲,若是没法在对象自己找到须要的属性或方法,就会继续访问对象的Prototype链。
var another = { a:2 }; //建立一个关联到anotherObject的对象 var myObj = Object.create(another); // 能找到,可是在myObj.__proto__上找到 myObj.a; //2 // Object.create(..)会建立一个对象并把这个对象的 [[Prototype]] 关联到指定的对象。
5.1.1 Object.prototype
通常原型链都会最终指向Object.prototype,而Object.prototype的__proto__为null。
5.1.2 属性设置和屏蔽
使用屏蔽得不偿失,因此应当尽可能避免使用
!!有些状况下会隐式产生屏蔽,必定要小心。
var anotherObject = { a:2 };
var myObject = Object.create( anotherObject );
anotherObject.a; // 2
myObject.a; // 2
anotherObject.hasOwnProperty( "a" ); // true
myObject.hasOwnProperty( "a" ); // false
myObject.a++; // 隐式屏蔽!
myObject.a; // 3
myObject.hasOwnProperty( "a" ); // true
++ 操做至关于 myObject.a = myObject.a + 1。所以 ++ 操做首先会经过 [[Prototype]] 查找属性 a 并从 anotherObject.a 获取当前属性值 2,而后给这个值加 1,接着用 [[Put]] 将值 3 赋给 myObject 中新建的屏蔽属性 a,天呐!
5.2 类
5.2.1 类函数
全部函数默认都会拥有一个名为prototype的公有且不可枚举属性,它其实指向另外一个对象。 同一个构造函数,及其构造的实例,它们对该函数prototype的引用其实指向同一段内存地址,修改这个prototype的属性及方法,会影响到这个构造函数及其全部实例。
5.5.2 构造函数
prototype默认有一个公有且不可枚举的属性constructor,这个属性引用的是对象关联的函数。
function foo() { // ... } foo.prototype.constructor === foo; // true var a = new foo(); // 并非a有这个属性,它是在原型链上找到的 a.constructor === foo; // true
须要注意的是,构造函数自己仍是函数,new操做符只是将其调用方式变成“构造函数调用”,本质上仍是须要去执行它。
在 JavaScript 中对于“构造函数”最准确的解释是,全部带 new 的函数调用。
5.2.3 技术
prototype中的constructor引用是很是不可靠,且不安全的,要避免使用。.constructor 并非一个不可变属性。它是不可枚举的,可是它的值 是可写的(能够被修改)。
实例对象 并无 .constructor 属性,因此它会委托__proto__链上的 Foo. prototype。
实际上,Foo 和你程序中的其余函数没有任何区别。函数自己并非构造函数,然而,当 你在普通的函数调用前面加上 new 关键字以后,就会把这个函数调用变成一个“构造函数 调用”。
new 会劫持全部普通函数并用构造对象的形式来调用它。
5.3 (原型)继承
要建立一个合适的关联对象,咱们必须使用 Object.create(..) 而不是使用具备副 做用的 Foo(..)。会建立一个新对象,再赋予当前对象
bar = Object.create( foo ); 中
Object.create(..),会建立一个新对象给bar,可是此时bar中并无内容,仍是指向的foo,能够引用foo中的属性;当foo中参数被修改时,bar的引用也会被修改;
可是当bar重写属性时,在当前实例中建立这个属性,重写的值不会改变foo的值!!!此后foo再次改变此属性不会再影响bar的属性值
ES6 添加了辅助函数 Object.setPrototypeOf(..),能够用标准而且可靠的方法来修 改关联。
// ES6 以前须要抛弃默认的 Bar.prototype Bar.ptototype = Object.create( Foo.prototype ); // ES6 开始能够直接修改现有的 Bar.prototype Object.setPrototypeOf( Bar.prototype, Foo.prototype )
a instanceof Foo; // true
instanceof 操做符的左操做数是一个普通的对象,右操做数是一个函数。instanceof 回答 的问题是:在 a 的整条 [[Prototype]] 链中是否有指向 Foo.prototype 的对象?
反过来操做就是:Foo.prototype.isPrototypeOf(a):foo是否出如今a的原型链中
//ES5 function Super(){ this.name = 'father'; } Super.prototype.say = function() { console.log('I am ' + this.name); }; function Sub(){ // 执行父类构造函数,得到属性 Super(); // 添加或覆盖属性 this.name = 'son'; } // 造成原型链 Sub.prototype = Object.create(Super.prototype); // 修复constructor指向 Sub.prototype.constructor = Sub; // 或者更直接的原型链 Sub.prototype.__proto__ = Super.prototype; //ES6提供一种新的操做方式 Object.setPrototypeOf(Sub.prototype, Super.prototype); // ES6 class A { constructor(){ this.name = 'father' } say() { console.log('Method from father: I am ' + this.name); } } class B extends A { constructor(){ super(); this.name = 'son' } say() { console.log(`Method from son: I am ${this.name}.`); super.say(); } }
另外一个须要注意的是非标准的proto,它实际上是一套getter/setter。
Object.defineProperty(Object.prototype, "__proto__", { get: function() { return Object.getPrototypeOf(this); }, set: function(o) { Object.setPrototypeOf(this, o); return o; }, });
5.4 对象关联
5.4.1 建立关联
//Object.create()的polyfill if(!Object.create){ Object.create = function(o) { function F(){} F.prototype = o; return new F(); }; }
5.4.2 关联关系是备用?
原型的实现应当遵循委托设计模式,API在原型链上出现的位置要参考现实中的状况。
第六章 行为委托
6.1 面向委托的设计
6.1.1 类理论
类设计模式鼓励你在继承时使用方法重写和多态。许多行为能够先抽象到父类,而后再用子类进行特殊化。
6.1.2 委托理论
首先定义对象,而不是类。经过Object.create()来建立委托对象,赋值给另外一个对象,从而让该对象出如今另外一个对象的原型链上。 好比var bar = Object.create(foo);,使得bar.__proto__===foo,从而bar能够经过原型链得到foo的全部属性和方法。 这种设计模式被称为对象关联。委托行为意味着某些对象在找不到属性或者方法引用时,会把这个请求委托给另外一个对象。
须要注意的是,禁止两个对象互相委托,不然当引用一个二者都不存在的属性或方法,会产生无限递归的循环。
function Foo() {} var a1 = new Foo(); a1; // Foo {} (chrome中) ; Object {} (Firefox中) //“{} 是一个空对象,由名为 Foo 的函数构造” ,由于 Chrome 会动态跟踪并把 实际执行构造过程的函数名看成一个内置属性,可是其余浏览器并不会跟踪这些额外的信息
6.1.3 比较思惟模型
对象关联风格相对于类风格更为简洁,由于它只关注对象之间的关联关系。
Foo = { init: function(who) { this.me = who; }, identify: function() { return 'I am ' + this.me; }, } Bar = Object.create(Foo); Bar.speak = function() { console.log('Hello, ' + this.identify() + '.'); }; var b1 = Object.create(Bar); var b2 = Object.create(Bar); // 从b1.__proto__.__proto__上获得init方法 b1.init('b1'); b2.init('b2'); // 从b1.__proto__上获得Bar的spaak方法 // 从b1.__proto__.__proto__上获得identify方法 b1.speak(); // Hello, b1. b2.speak(); // Hello, b2.
6.2.2 委托控件对象
委托模式能够防止上一节中不太合理的伪多态形式调用Widget.prototype.render.call(this, $where);。 同时init和setup两个初始化函数更具语义表达能力,同时因为将构建和初始化分开,使得初始化的时机变得更灵活,容许异步调用。 对象关联能够更好地支持关注分离原则,建立和初始化不须要合并为一个步骤。
6.3 更简洁的设计
对象关联除了能让代码看起来更简洁,更具扩展性,还能够经过委托模式简化代码结构。
6.4 更好的语法
ES6中使用Object.setPrototypeOf(target, obj)的方式来简化target = Object.create(obj)的写法。
6.5 内省
内省就是检查实例的类型。 instanceof实际上检查的是目标对象与构造器原型的关系,对于经过委托互相关联的对象(Foo与Bar互相关联),可使用: