彻底搞懂JavaScript之原型

1. [[Prototype]]

JavaScript 中的对象有一个特殊的 [[Prototype]] 内置属性,其实就是对于其他对象的引用。几乎所有的对象在创建时 [[Prototype]] 属性都会被赋予一个非空的值

对象的 [[Prototype]] 链接可以为空,虽然很少见

Object.create(..)会创建一个对象并把这个对象的 [[Prototype]] 关联到指定的对象。

使用 for..in 遍历对象时原理和查找 [[Prototype]] 链类似,任何可以通过原型链访问到(并且是 enumerable)的属性都会被枚举。使用 in操作符来检查属性在对象中是否存在时,同样会查找对象的整条原型链(无论属性是否可枚举),之所以我们使用for...in时没有遍历到原型链上的属性,是因为他们不可枚举

1.1 Object.prototype

所有普通的 [[Prototype]] 链最终都会指向内置的 Object.prototype。

JavaScript 的[[Prototype]] 机制本质上就是行为委托机制。 委托行为意味着某些对象(XYZ)在找不到属性或者方法引用时会把这个请求委托给另一个对象(Task)。
这是一种极其强大的设计模式,和父类、子类、继承、多态等概念完全不同。在你的脑海中对象并不是按照父类到子类的关系垂直组织的,而是通过任意方向的委托关联并排组织的。

1.2属性设置和屏蔽

myObject.foo = "bar"
复制代码

如果 foo 不是直接存在于 myObject 中,[[Prototype]] 链就会被遍历,类似 [[Get]] 操作。如果原型链上找不到 foo,foo 就会被直接添加到 myObject 上。

然而,如果 foo 存在于原型链上层,赋值语句 myObject.foo = "bar" 的行为就会有些不同

如果属性名 foo 既出现在 myObject 中也出现在 myObject 的 [[Prototype]] 链上层,那么就会发生屏蔽。myObject 中包含的 foo 属性会屏蔽原型链上层的所有 foo 属性,因为myObject.foo 总是会选择原型链中最底层的 foo 属性。

屏蔽有三种情况:https://www.bugzj.com/486.html

  1. 如果在 [[Prototype]] 链上层存在名为 foo 的普通数据访问属性并且没有被标记为只读(writable:false),那就会直接在 myObject 中添加一个名为 foo 的新属性,它是屏蔽属性。
  2. 如果在 [[Prototype]] 链上层存在 foo,但是它被标记为只读(writable:false),那么无法修改已有属性或者在 myObject 上创建屏蔽属性。如果运行在严格模式下,代码会抛出一个错误。否则,这条赋值语句会被忽略。总之,不会发生屏蔽。
  3. 如果在 [[Prototype]] 链上层存在 foo 并且它是一个 setter,那就一定会调用这个 setter。foo 不会被添加到(或者说屏蔽于)myObject,也不会重新定义 foo 这 个 setter。

如果你希望在第二种和第三种情况下也屏蔽 foo,那就不能使用 = 操作符来赋值,而是使用 Object.defineProperty(..)来向 myObject 添加 foo

第二种情况可能是最令人意外的,只读属性会阻止 [[Prototype]] 链下层隐式创建(屏蔽)同名属性。这样做主要是为了模拟类属性的继承。你可以把原型链上层的 foo 看作是父类中的属性它会被 myObject 继承(复制),这样一来 myObject 中的 foo 属性也是只读,所以无法创建

有些情况下会隐式产生屏蔽:

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++; // 隐式屏蔽!

anotherObject.a; // 2 
myObject.a; // 3 
myObject.hasOwnProperty( "a" ); // true
复制代码

myObject.a++ 看起来应该(通过委托)查找并增加 anotherObject.a 属性,但是别忘了 ++ 操作相当于 myObject.a = myObject.a + 1。因此 ++ 操作首先会通过 [[Prototype]]查找属性 a 并从 anotherObject.a 获取当前属性值 2,然后给这个值加 1,接着用 [[Put]]将值 3 赋给 myObject 中新建的屏蔽属性 a

修改委托属性时一定要小心。如果想让 anotherObject.a 的值增加,唯一的办法是anotherObject.a++

2. 类

2.1 类函数

所有的函数默认都会拥有一个名为 prototype 的公有并且不可枚举的属性,它会指向另一个对象

function Foo() { 
    // ... 
} 
Foo.prototype; // { }
复制代码

这个对象通常被称为 Foo 的原型,因为我们通过名为 Foo.prototype 的属性引用来访问它

function Foo() { 
    // ... 
} 
var a = new Foo(); 
Object.getPrototypeOf( a ) === Foo.prototype; // true
复制代码

调用 new Foo() 时会创建 a,其中一步就是将 a 内部的[[Prototype]] 链接,关联到 Foo.prototype 所指向的对象

在面向类的语言中,类可以被复制(或者说实例化)多次,就像用模具制作东西一样。
但是在 JavaScript 中,并没有类似的复制机制。你不能创建一个类的多个实例,只能创建多个对象,它们 [[Prototype]] 关联的是同一个对象。但是在默认情况下并不会进行复制,因此这些对象之间并不会完全失去联系,它们是互相关联的。
new Foo() 会生成一个新对象(我们称之为 a),这个新对象的内部链接 [[Prototype]] 关联的是 Foo.prototype 对象。
最后我们得到了两个对象,它们之间互相关联,就是这样。我们并没有初始化一个类,实际上我们并没有从“类”中复制任何行为到一个对象中,只是让两个对象互相关联。
new Foo() 这个函数调用实际上并没有直接创建关联,这个关联只是一个意外的副作用。new Foo()只是间接完成了我们的目标:一个关联到其他对象的新对象。这个机制通常被称为原型继承

更直接的方法是使用Object.create()

2.2 构造函数

function Foo() { 
    // ... 
} 
Foo.prototype.constructor === Foo; // true 
var a = new Foo(); 
a.constructor === Foo; // true
复制代码

Foo.prototype 默认(在代码中第一行声明时!)有一个公有并且不可枚举的属性.constructor,这个属性引用的是对象关联的函数

通过“构造函数”调用 new Foo() 创建的对象也有一个 .constructor 属性,指向“创建这个对象的函数”。
实际上 a 本身并没有 .constructor 属性。而且,虽然 a.constructor 确实指向 Foo 函数,但是这个属性并不是表示 a 由 Foo“构造”
实际上,.constructor 引用同样被委托给了 Foo.prototype,而Foo.prototype.constructor 默认指向 Foo。

当你在普通的函数调用前面加上 new 关键字之后,就会把这个函数调用变成一个“构造函数调用”。实际上,new 会劫持所有普通函数并用构造对象的形式来调用它。

function NothingSpecial() { 
    console.log( "Don't mind me!" ); 
} 
var a = new NothingSpecial(); 
// "Don't mind me!" 
a; // {}
复制代码

NothingSpecial 只是一个普通的函数,但是使用 new 调用时,它就会构造一个对象并赋值给 a,这看起来像是 new 的一个副作用(无论如何都会构造一个对象)。这个调用是一个构造函数调用,但是 NothingSpecial本身并不是一个构造函数。
换句话说,在 JavaScript 中对于“构造函数”最准确的解释是,所有带 new 的函数调用。
函数不是构造函数,但是当且仅当使用 new 时,函数调用会变成“构造函数调用”

Foo.prototype 的 .constructor 属性只是 Foo 函数在声明时的默认属性。如果你创建了一个新对象并替换了函数默认的 .prototype对象引用,那么新对象并不会自动获得 .constructor 属性

function Foo() { /* .. */ } 
Foo.prototype = { /* .. */ }; // 创建一个新原型对象
var a1 = new Foo(); 
a1.constructor === Foo; // false! 
a1.constructor === Object; // true!
复制代码

构造函数执行过程:

首先我们通过一段代码来看下new执行做了什么:

function func() {
    // let obj={}; //=>这个对象就是实例对象
    // this -> obj
    let x = 100;
    this.num = x + 100; //=>相当于给创建的实例对象新增一个num的属性 obj.num=200 (因为具备普通函数执行的一面,所以只有this.xxx=xxx才和创建的实例有关系,此案例中的x只是AO中的私有变量
    // return obj;  用户自己返回内容,如果返回的是一个引用类型值,则会把默认返回的实例给覆盖掉(此时返回的值就不在是类的实例了)
}
let f = new func();
console.log(f); //=>f是func这个类的实例 {num:200}

let f2 = new func();
console.log(f === f2); //=>false 每一次new出来的都是一个新的实例对象(一个新的堆内存)

console.log(f instanceof func); //=>TRUE instanceof用来检测某一个实例是否属于这个类

func(); //=>this:window  AO(FUNC):{x=100} ... 普通函数执行
复制代码

以该函数为例,我们来分析下构造函数执行过程:

function Person(name, gender, hobby) {
    this.name = name;
    this.gender = gender;
    this.hobby = hobby;
    this.age = 6;
}

var p1 = new Person('zs', '男', 'basketball');
var p2 = new Person('ls', '女', 'dancing');
复制代码

(1) 当以 new 关键字调用时,会创建一个新的内存空间,标记为 Person 的实例。

创建内存空间 (2) 函数体内部的 this 指向该内存 函数体内部指向该内存 通过以上两步,我们就可以得出这样的结论。
var p2 = new Person('ls', '女', 'dancing');  // 创建一个新的内存 #f2
var p3 = new Person('ww', '女', 'singing');  // 创建一个新的内存 #f3
复制代码

每当创建一个实例的时候,就会创建一个新的内存空间(#f2, #f3),创建 #f2 的时候,函数体内部的 this 指向 #f2, 创建 #f3 的时候,函数体内部的 this 指向 #f3。

(3) 执行函数体内的代码
通过上面的讲解,你就可以知道,给 this 添加属性,就相当于给实例添加属性。

(4) 默认返回 this 。 由于函数体内部的 this 指向新创建的内存空间,默认返回 this ,就相当于默认返回了该内存空间,也就是上图中的 #f1。此时,#f1的内存空间被变量 p1 所接受。也就是说 p1 这个变量,保存的内存地址就是 #f1,同时被标记为 Person 的实例。如果我们返回一个基础类型值,那么最终返回的依然是this,如果我们返回一个引用类型值,那么就返回该引用类型值。

以上构造函数参考:
JS进阶(1) —— 人人都能懂的构造函数

2.3 constructor

可以给 Foo.prototype 添加一个 .constructor 属性,不过这需要手动添加一个符合正常行为的不可枚举属性

function Foo() { /* .. */ } 
Foo.prototype = { /* .. */ }; // 创建一个新原型对象
// 需要在 Foo.prototype 上“修复”丢失的 .constructor 属性
// 新对象属性起到 Foo.prototype 的作用
// 关于 defineProperty(..),参见第 3 章
Object.defineProperty( Foo.prototype, "constructor" , { 
    enumerable: false, 
    writable: true, 
    configurable: true, 
    value: Foo // 让 .constructor 指向 Foo 
} );
复制代码

对象的 .constructor 会默认指向一个函数,这个函数可以通过对象的 .prototype引用

.constructor 并不是一个不可变属性。它是不可枚举(参见上面的代码)的,但是它的值是可写的(可以被修改)。此外,你可以给任意 [[Prototype]] 链中的任意对象添加一个名为 constructor 的属性或者对其进行修改,你可以任意对其赋值。

3. 继承

function Foo(name) { 
    this.name = name; 
} 
Foo.prototype.myName = function() { 
    return this.name; 
}; 
function Bar(name,label) { 
    Foo.call( this, name ); 
    this.label = label; 
} 
// 我们创建了一个新的 Bar.prototype 对象并关联到 Foo.prototype 
Bar.prototype = Object.create( Foo.prototype ); 
// 注意!现在没有 Bar.prototype.constructor 了
// 如果你需要这个属性的话可能需要手动修复一下它
Bar.prototype.myLabel = function() { 
    return this.label; 
}; 
var a = new Bar( "a", "obj a" ); 
a.myName(); // "a" 
a.myLabel(); // "obj a"
复制代码

Bar.prototype = Object.create( Foo.prototype )。调用Object.create(..) 会凭空创建一个“新”对象并把新对象内部的 [[Prototype]] 关联到你指定的对象

下面这两种方式是常见的错误做法,实际上它们都存在一些问题:

// 和你想要的机制不一样!
Bar.prototype = Foo.prototype; 
// 基本上满足你的需求,但是可能会产生一些副作用
Bar.prototype = new Foo();
复制代码

Bar.prototype = Foo.prototype 并不会创建一个关联到 Bar.prototype 的新对象,它只是让 Bar.prototype 直接引用 Foo.prototype 对象。因此当你执行类似 Bar.prototype.myLabel = ... 的赋值语句时会直接修改 Foo.prototype 对象本身

Bar.prototype = new Foo() 的确会创建一个关联到 Bar.prototype 的新对象。但是它使用了 Foo(..) 的“构造函数调用”,如果函数 Foo 有一些副作用(比如写日志、修改状态、注册到其他对象、给 this 添加数据属性,等等)的话,就会影响到 Bar() 的“后代”

因此,要创建一个合适的关联对象,我们必须使用 Object.create(..) 而不是使用具有副作用的 Foo(..)。这样做唯一的缺点就是需要创建一个新对象然后把旧对象抛弃掉,不能直接修改已有的默认对象

ES6 添加了辅助函数 Object.setPrototypeOf(..),可以用标准并且可靠的方法来修改关联。 我们来对比一下两种把 Bar.prototype 关联到 Foo.prototype 的方法:

// ES6 之前需要抛弃默认的 Bar.prototype 
Bar.ptototype = Object.create( Foo.prototype ); 
// ES6 开始可以直接修改现有的 Bar.prototype 
Object.setPrototypeOf( Bar.prototype, Foo.prototype );
复制代码

如果忽略掉 Object.create(..) 方法带来的轻微性能损失(抛弃的对象需要进行垃圾回收),它实际上比 ES6 及其之后的方法更短而且可读性更高。不过无论如何,这是两种完全不同的语法。

3.1 检查类关系

  1. instanceof
function Foo() { 
    // ... 
} 
Foo.prototype.blah = ...; 
var a = new Foo();

a instanceof Foo; // true
复制代码

a 的整条 [[Prototype]] 链中是否有指向 Foo.prototype 的对象? instanceof只能处理对象(a)和函数(带 .prototype 引用的 Foo)之间的关系。如果你想判断两个对象(比如 a 和 b)之间是否通过 [[Prototype]] 链关联,只用 instanceof无法实现。

内置的 .bind(..) 函数来生成的硬绑定函数数是没有 .prototype 属性的。在这样的函数上使用 instanceof 的话, 目标函数的 .prototype 会代替硬绑定函数的 .prototype。

// 用来判断 o1 是否关联到(委托)o2 的辅助函数
function isRelatedTo(o1, o2) { 
function F(){} 
    F.prototype = o2; 
    return o1 instanceof F; 
} 
var a = {}; 
var b = Object.create( a ); 
isRelatedTo( b, a ); // true
复制代码

在 isRelatedTo(..) 内部我们声明了一个一次性函数 F,把它的 .prototype 重新赋值并指向对象 o2,然后判断 o1 是否是 F 的一个“实例”。显而易见,o1 实际上并没有继承 F 也不是由 F 构造,所以这种方法非常愚蠢并且容易造成误解

  1. isPrototypeOf
Foo.prototype.isPrototypeOf( a ); // true
复制代码

isPrototypeOf(..) 回答的问题是:在 a 的整条 [[Prototype]] 链中是否出现过 Foo.prototype

我们只需要两个对象就可以判断它们之间的关系。举例来说:

// 非常简单:b 是否出现在 c 的 [[Prototype]] 链中?
b.isPrototypeOf( c );
复制代码

语言内置的 isPrototypeOf(..) 函数就是我们的 isRelatedTo(..) 函数

我们也可以直接获取一个对象的 [[Prototype]] 链。

Object.getPrototypeOf( a ) === Foo.prototype; // true
复制代码
  1. __proto__
    绝大多数(不是所有!)浏览器也支持一种非标准的方法来访问内部 [[Prototype]] 属性:
a.__proto__ === Foo.prototype; // true
复制代码

__proto__ 看起来很像一个属性,但是实际上它更像一个 getter/setter

Object.defineProperty( Object.prototype, "__proto__", { 
    get: function() { 
        return Object.getPrototypeOf( this ); 
    }, 
    set: function(o) { 
        // ES6 中的 setPrototypeOf(..) 
        Object.setPrototypeOf( this, o ); 
    return o; 
 } 
} );
复制代码

4. 对象关联

4.1 创建关联

Object.create(null) 会 创 建 一 个 拥 有 空( 或 者 说 null)[[Prototype]]链接的对象,这个对象无法进行委托。由于这个对象没有原型链,所以instanceof 操作符(之前解释过)无法进行判断,因此总是会返回 false。这些特殊的空 [[Prototype]] 对象通常被称作“字典”,它们完全不会受到原型链的干扰,因此非常适合用来存储数据。

polyfill中Object.create(..)实现

if (!Object.create) { 
    Object.create = function(o) { 
    function F(){} 
        F.prototype = o; 
        return new F(); 
    }; 
}
复制代码
var anotherObject = { 
    a:2 
}; 
var myObject = Object.create( anotherObject, { 
    b: { 
        enumerable: false, 
        writable: true, 
        configurable: false, 
    value: 3 
    }, 
    c: { 
        enumerable: true, 
        writable: false, 
        configurable: false, 
        value: 4 
    } 
}); 
myObject.hasOwnProperty( "a" ); // false 
myObject.hasOwnProperty( "b" ); // true 
myObject.hasOwnProperty( "c" ); // true 
myObject.a; // 2 
myObject.b; // 3 
myObject.c; // 4
复制代码

Object.create(..) 的第二个参数指定了需要添加到新对象中的属性名以及这些属性的属性描述符。因为 ES5 之前的版本无法模拟属性操作符,所以 polyfill 代码无法实现这个附加功能。

4.2 关联关系是备用

var anotherObject = { 
    cool: function() { 
        console.log( "cool!" ); 
    } 
}; 
var myObject = Object.create( anotherObject ); 
myObject.cool(); // "cool!"
复制代码

如果你这样写只是为了让myObject 在无法处理属性或者方法时可以使用备用的 anotherObject,对于未来维护你 软件的开发者来说这可能不太好理解

委托设计模式:

var anotherObject = { 
    cool: function() { 
        console.log( "cool!" ); 
    } 
}; 
var myObject = Object.create( anotherObject ); 
myObject.doCool = function() { 
this.cool(); // 内部委托!
}; 
myObject.doCool(); // "cool!"
复制代码

5. 原型链

原型链示意图

所有的引用类型(数组、对象、函数),都有一个_proto_(隐式原型) 属性,属性值是一个普通的对象,__proto__指向创建该实例函数的prototype

所有的函数,都有一个prototype(显式原型)属性,属性值也是一个普通的对象。

当试图得到一个对象的某个属性时,如果这个对象本身没有这个属性,那么会去它的__proto__(即它的构造函数的prototype)中寻找。

var obj = {}; obj.a = 100;
var arr = []; arr.a = 100;
function fn () {}
fn.a = 100;

console.log(obj.__proto__);
console.log(arr.__proto__);
console.log(fn.__proto__);

console.log(fn. prototype)
console.log(obj.__proto__===Object.prototype)
复制代码
function f1() {}
console.log(typeof f1.prototype)  // Object
console.log(typeof Function.prototype)  // Function
console.log(typeof Object.prototype)  // Object
console.log(typeof Function.prototype.prototype)  // Object
console.log(f1.__proto__ === Function.prototype) // true
复制代码
// 万物皆对象
Object.prototype.name = "zxhnext";
1.0.name  // zxhnext
1..name  // zxhnext
1.name  // 报错 因为不知道 . 属于1还是属于name,所以报错
Function.name  // Function function的原型链与object不在一起,所以打出函数名字
复制代码

5.1 原型继承

var father = function(color) {
    // constructor == Car 构造函数和初始化这个类是一个东西, constructor即函数本身
    this.color = color
    console.log(111)
}
father.prototype.sail = function() {
    // 挂载到原型上new的时候可以少实例化一次,省去重新构建的成本
    console.log('这是'+this.color+'色')
}
var son = function(color) {
    father.call(this,color) // 绑定this
}
// son.prototype = father.prototype // 子类修改时会修改父类,因为这是按引用传递
// new的时候构造函数会执行
// 1. 拿到父类原型链上的方法
// 2. 引用的原型链不能按址引用
// 3. 修正子类的constructor
var __pro = Object.create(father.prototype);
__pro.constructor = son;
son.prototype = __pro;
复制代码

es6继承:

// es6继承
class People{
    constructor(name,age){
        this.name = name;
        this.age = age;
    }
    getName(){
        return this.name;
    }
}
class English extends People{
    constructor(name,age,language){
        super(name,age)
        this.language = language;
    }
    introduce(){
        console.log("hi,I am" + this.getName())
        console.log("I speak" + this.language)
    }
} 
复制代码

6. 考点

6.1 实现一个new

根据以上构造函数执行过程,我们先来分析一下:

  1. 形成一个全新的执行上下文EC
  2. 形成一个AO变量对象
  3. ARGUMENTS
  4. 形参赋值
  5. 初始化作用域链
  6. [新]默认创建一个对象,而这个对象就是当前类的实例
  7. [新]声明其THIS指向,让其指向这个新创建的实例
  8. 代码执行
  9. [新]不论其是否写RETURN,都会把新创建的实例返回(特殊点)
function _new(Func, ...args) {
    //默认创建一个实例对象(而且是属于当前这个类的一个实例)
    // let obj = {};
    // obj.__proto__ = Func.prototype; //=>IE大部门浏览器中不允许我们直接操作__proto__
    let obj = Object.create(Func.prototype);
    
    //也会把类当做普通函数执行
    //执行的时候要保证函数中的this指向创建的实例
    let result = Func.call(obj, ...args);
    
    //若客户自己返回引用值,则以自己返回的为主,否则返回创建的实例
    if ((result !== null && typeof result === "object") || (typeof result === "function")) {
        return result;
    }
    return obj;
}
复制代码

6.2 面试题

1

let obj = {
    2: 3,
    3: 4,
    length: 2,
    push: Array.prototype.push
}
obj.push(1);
obj.push(2);
console.log(obj);
复制代码

2

var a = ?;
if (a == 1 && a == 2 && a == 3) {
    console.log('OK');
}

// 1.
var a = [1, 2, 3];
a.toString = a.shift;
if (a == 1 && a == 2 && a == 3) {
	console.log('OK');
}
// 2.
var a = {
	i: 0,
	toString() {
		return ++this.i;
	}
};
//=>a == 1 :a.toString()
if (a == 1 && a == 2 && a == 3) {
	console.log('OK');
}
// 3. 
var i = 0;
Object.defineProperty(window, 'a', {
	get() {
		//=>获取window.a的时候触发
		return ++i;
	},
	set() {
		//=>给window.a设置属性值的时候触发
	}
});
if (a == 1 && a == 2 && a == 3) {
	console.log('OK');
}
复制代码

3

function C1(name) {
    if (name) {
        this.name = name;
    }
}
function C2(name) {
    this.name = name;
}
function C3(name) {
    this.name = name || 'join';
}
C1.prototype.name = 'Tom';
C2.prototype.name = 'Tom';
C3.prototype.name = 'Tom';
alert((new C1().name) + (new C2().name) + (new C3().name));
复制代码

4

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);
}
Foo.getName();
getName();
Foo().getName();
getName();
new Foo.getName();
new Foo().getName();
new new Foo().getName();
复制代码

5

function fun(){
    this.a=0;
    this.b=function(){
        alert(this.a);
    }
}
fun.prototype={
    b:function(){
        this.a=20;
        alert(this.a);
    },
    c:function(){
        this.a=30;
        alert(this.a)
    }
}
var my_fun=new fun();
my_fun.b();
my_fun.c();
复制代码

6

let n = 10;
let m = n.plus(10).minus(5);
console.log(m);//=>15(10+10-5)

~ function anonymous(proto) {
	const checkNum = function checkNum(num) {
		num = Number(num);
		if (isNaN(num)) {
			num = 0;
		}
		return num;
	};
	proto.plus = function plus(num) {
		//=>this:我们要操作的那个数字实例(对象)
		//=>返回Number类的实例,实现链式写法
		return this + checkNum(num);
	};
	proto.minus = function minus(num) {
		return this - checkNum(num);
	};
}(Number.prototype);

复制代码

7

function Fn() {
    this.x = 100;
    this.y = 200;
    this.getX = function () {
        console.log(this.x);
    }
}
Fn.prototype.getX = function () {
    console.log(this.x);
};
Fn.prototype.getY = function () {
    console.log(this.y);
};
let f1 = new Fn;
let f2 = new Fn;
console.log(f1.getX === f2.getX);
console.log(f1.getY === f2.getY);
console.log(f1.__proto__.getY === Fn.prototype.getY);
console.log(f1.__proto__.getX === f2.getX);
console.log(f1.getX === Fn.prototype.getX);
console.log(f1.constructor);
console.log(Fn.prototype.__proto__.constructor);
f1.getX();
f1.__proto__.getX();
f2.getY();
Fn.prototype.getY();
复制代码

8

~ function () {
    function call(context) {
        context = context || window;
        let args = [].slice.call(arguments,1),
            result;
        context.$fn = this;
        result = context.$fn(...args);
        delete context.$fn;
        return result;
    }
    Function.prototype.call = call;
}();

function fn1(){console.log(1);}
function fn2(){console.log(2);}
fn1.call(fn2);
fn1.call.call(fn2);
Function.prototype.call(fn1);
Function.prototype.call.call(fn1);
复制代码

9

function Fn() {
    this.x = 100;
    this.y = 200;
    this.getX = function () {
        console.log(this.x);
    }
}
Fn.prototype.getX = function () {
    console.log(this.x);
};
Fn.prototype.getY = function () {
    console.log(this.y);
};
let f1 = new Fn;
let f2 = new Fn;
console.log(f1.getX === f2.getX);
console.log(f1.getY === f2.getY);
console.log(f1.__proto__.getY === Fn.prototype.getY);
console.log(f1.__proto__.getX === f2.getX);
console.log(f1.getX === Fn.prototype.getX);
console.log(f1.constructor);
console.log(Fn.prototype.__proto__.constructor);
f1.getX();
f1.__proto__.getX();
f2.getY();
Fn.prototype.getY();
原文地址:https://www.cnblogs.com/Dplus/p/13307320.html