557 原型prototype和原型链__proto__:原理,函数的三种角色,for in,手写new

向对象底层运行机制的三句话

* 面向对象底层运行机制的三句话:

* 1.每一个函数(普通函数、内置类/构造函数、自定义类/构造函数)都具备一个属性: prototype[原型],属性值是一个对象[浏览器内置开辟的堆],在这个对象中存储的是,当前类供应给实例调用的公共属性、方法。【自定义类就是通过class创建的类,以及通过function 函数名创建的构造函数。prototype是对象,不是函数。】

* 2.在prototype这个对象中,内置一个constructor属性[类的构造函数],属性值是当前类本身。

* 3.每一个对象(普通对象、数组对象、实例、prototype也是对象...)都具备一个属性:__proto__[原型链],属性值是当前对象(实例)所属类的prototype。

function fn() {
  console.log('哈哈哈');
}

console.log('fn---', fn.prototype);
console.log('fn---', fn.__proto__);
console.log('fn---', fn.prototype == fn.__proto__); // false
console.log('fn---', Function.prototype == fn.__proto__); // true,fn是Function的实例

const f = new fn();
console.log('f---', f.prototype); // undefined,对象没有prototype,函数才有
console.log('f---', f.__proto__); //
console.log('f---', f.prototype == f.__proto__); // false
console.log('f---', fn.prototype == f.__proto__); // true

function Func() {
  this.name = 'xxx';
  this.age = 20;
  this.say = function say() {
    console.log(`my name is ${this.name},i'm ${this.age} years old!`);
  };
}
Func.prototype.say = function say() {
  console.log('say prototype');
};
Func.prototype.eat = function eat() {
  console.log('eat prototype');
};
var f1 = new Func;
var f2 = new Func;

原型和原型链练习题1

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); // false
console.log(f1.getY === f2.getY); // true
console.log(f1.__proto__.getY === Fn.prototype.getY); // true
console.log(f1.__proto__.getX === f2.getX); // false
console.log(f1.getX === Fn.prototype.getX); // false
console.log(f1.constructor); // Fn
console.log(Fn.prototype.__proto__.constructor); // Object
f1.getX(); // 100
f1.__proto__.getX(); // undefined
f2.getY(); // 200
Fn.prototype.getY(); // undefined

原型和原型链练习题2

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(); // 字符串0
my_fun.c(); // 字符串30
console.log(my_fun.a) // 30

深入原型,体验函数的三种角色

我的总结:

1、Function是Function自己的实例对象,所以Function的__proto__指向Function.prototype。

2、Object是Function的实例对象,所以Object的__proto__指向Function.prototype。

3、Function.prototype是一个对象,所以Function.prototype的__proto__指向Object.prototype。

4、所有函数都是Function的实例,所以函数的__proto__指向Function.prototype。

5、Function.prototype是一个匿名空函数。


基于内置类的原型扩展方法,解决for in弊端

向内置类的原型上扩展方法,存在的细节知识:

  • 1.为了防止自己设定的方法覆盖内置的方法,我们设置的方法名加前缀
  • 2.优势:使用起来方便,和内置方法类似,直接让实例调用即可
  • 3.方法中的this一般是当前要操作的实例(也就不需要基于形参传递实例进来了)
  • 4.优势:只要保证方法的返回结果还是当前类的实例,那么我们就可以基于“链式方法”调用当前类中提供的其它方法【返回结果是谁的实例,就可以继续调用谁的方法】

for of :用于有 Symbol.iterator 的数据结构

数组、类数组、new Set、map、字符串,对象不具备Symbol.iterator

Array.prototype.myDistinct = function myDistinct() {
  // this -> arr
  // ES6中的Set结构(不重复的数组):Set类的实例
  // let newArr = [...new Set(this)]; // 方法1
  let newArr = Array.from(new Set(this)); // 方法2
  return newArr;
};
let arr = [1, 2, 3, 2, 3, 2, 1, 2, 3, 4, 3, 2, 1];
arr = arr.myDistinct().reverse().map(item => item * 10);
console.log(arr);


// ---------------


/* 
FOR IN 遍历对象【的弊端】:
所有可以被枚举的属性都可以遍历到(大部分私有属性和自己向内置类原型上扩展的属性,私有属性length不能遍历)。
解决方案:
(1)处理 for in 循环的时候,我们需要加hasOwnProperty判断;
(2)使用Object.keys。
*/
Object.prototype.myXx = function () { };
let obj = {
  name: 'xxx',
  age: 20
};

for (let key in obj) {
  if (!obj.hasOwnProperty(key)) break;
  console.log(key, obj[key]);
}

// 我的写法
for (let k in obj) {
  if (obj.hasOwnProperty(k)) console.log(k, obj[k])
}

// 这样也可以,Object.keys(obj)只会获取所有私有的属性
Object.keys(obj).forEach(key => console.log(key, obj[key]));

手写new和Object.create

  • Func:即将创建的类
  • args:给当前这个类执行时候传递的实参
    1. 拥有普通函数执行的一面,让他作为普通函数执行
    2. 创建一个实例对象
      -> 空对象
      -> 对象.proto===类.prototype
    3. 方法执行的时候,方法中的this是实例对象
    4. 判断方法的返回结果,如果返回的不是引用类型值,默认把实例返回

Object.create(xxx):创建一个空对象,并且让把xxx作为创建对象的原型(空对象.__proto __ = xxx),xxx必须是对象或者null,如果xxx是null,则创建一个没有任何原型指向的空对象

我的版本
  1. 在构造函数代码开始执行之前,创建一个空对象;
  2. 设置新对象的__proto__指向构造函数的prototype对象,p.proto = XXXX.prototype;
  3. 执行构造函数,修改this的指向,让构造函数中的this指向创建出来的空对象,拷贝构造函数中的方法、属性给新对象;
  4. 判断构造函数返回的结果,如果返回的不是引用类型值,默认把实例返回;如果返回的是引用类型值,则返回引用类型。
// 有些简单函数就不用再画开辟堆保存函数的图
// Object.create处理兼容
Object.create = function create(prototype) {
  function Fn() { }; // 【创建一个空对象】
  Fn.prototype = prototype; // 【让空对象的prototype指向prototype】
  Fn.prototype.constructor = Fn
  return new Fn; // 【返回一个实例对象】

  // 也可以下面这样写,但是不推荐用__proto__
  // Fn.__proto__ = prototype
  // return Fn
};

function _new(Func, ...args) {
  // 【把Func.prototype作为新对象obj的原型,即obj.__proto__ = Func.prototype】
  let obj = Object.create(Func.prototype);
  // 【执行Func,让Func中的this指向obj。】
  let result = Func.call(obj, ...args);
  // 【不是引用类型值,返回obj。undefined == null。】
  if (result == null || !/^(object|function)$/.test(typeof result)) return obj;
  return result;
}


function Dog(name) {
  this.name = name;
}
Dog.prototype.bark = function () {
  console.log('wangwang');
};
Dog.prototype.sayName = function () {
  console.log('my name is ' + this.name);
};

let sanmao = _new(Dog, '三毛');
sanmao.bark(); //=>"wangwang"
sanmao.sayName(); //=>"my name is 三毛"
console.log(sanmao instanceof Dog); //=>true

练习题1

(function (prototype) {
  function validateNum(num) {
    num = Number(num);
    // 不能用 typeof num == 'NaN'判断,因为 typeof NaN 是"number"。可以用 Number.isNaN(num)
    return isNaN(num) ? 0 : num;
  }
  prototype.plus = function plus(num) {
    // this  =>  对象数据类型的值
    num = validateNum(num);
    return this + num; // 直接 return this + validateNum(num) ,一行代码搞定
  };
  prototype.minus = function minus(num) {
    num = validateNum(num);
    return this - num;
  };
})(Number.prototype);

// 百度面试题
// 完成如下的需求
let n = 10;
let m = n.plus(10).minus(5);
console.log(m); // => 15(10+10-5) 

练习题2

== 比较的时候,如果两边类型不一样:

null == undefined,和其它值都不会相等

对象 == 字符串:对象转换为字符差

【其他】都转换为数字

a等于什么值会让下面条件成立
var a = ?;
if (a == 1 && a == 2 && a == 3) {
    console.log('OK');
}
// 方法1:valueOf,也可以把valueOf改为toString
var a = {
  n: 0,
  valueOf() {
    return ++this.n;
  }
};
if (a == 1 && a == 2 && a == 3) {
  console.log('OK');
}


// ------------------------


// 方法2:toString、shift
var a = [1, 2, 3];
a.toString = a.shift;
if (a == 1 && a == 2 && a == 3) {
  console.log('OK');
}


// ------------------------


// 方法3:利用数据劫持完成,Object.defineProperty / Proxy 
let n = 0;
// 可以把 window 改为 globalThis ,这样不是在浏览器环境里也可以运行
Object.defineProperty(window, 'a', {
  get() {
    return ++n;
  }
});
if (a == 1 && a == 2 && a == 3) {
  console.log('OK');
} 

练习题3

下面代码输出的结果
let obj = {
    2: 3,
    3: 4,
    length: 2,
    push: Array.prototype.push
}
obj.push(1);
obj.push(2);
console.log(obj);
/* Array.prototype.push = function push(item) {
  // this  =>  arr(实例)
  // 1. 向实例的末尾追加一个新值
  // this[this.length]=item;
  // 2. 需要把实例的length累加1
  // this.length++;
  // 3. 返回新增后实例的length
};
let arr = [10, 20];
arr.push(30); */

let obj = {
  2: 3,
  3: 4,
  length: 2,
  push: Array.prototype.push
}
obj.push(1);
// => this:obj  
// obj[obj.length]=1   obj[2]=1
// obj.length++  
// => {2:1,3:4,length:3...}

obj.push(2);
// => this:obj
// obj[obj.length]=2   obj[3]=2
// obj.length++  
// => {2:1,3:2,length:4...}

console.log(obj); // {2: 1, 3: 2, length: 4, push: ƒ}

练习题4

function C1(name) {
  // 没有设置私有属性
  if (name) {
    this.name = name;
  }
}

function C2(name) {
  this.name = name;
  // this.name=undefined;
}

function C3(name) {
  this.name = name || 'join';
  // this.name='join';
}

C1.prototype.name = 'Tom';
C2.prototype.name = 'Tom';
C3.prototype.name = 'Tom';

// (1)都没传参,Tomundefinedjoin;(2)new C2()、成员访问的优先级都是19,new Fun的是18
alert((new C1().name) + (new C2().name) + (new C3().name)); 

练习题5

/*
 * 函数的三种角色
 *    1.普通函数(上下文/作用域/形参赋值/作用域链)
 *    2.构造函数(实例/原型/原型链) 
 *    3.普通对象(属性名和属性值)
 * 三种角色没有必然的联系
 */
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();
// 普通函数,执行的返回结果,再调用getName
Foo().getName();
getName();
// 【先成员访问,得到函数Foo.getName,此时没执行;然后new,new会执行函数。不是先得到函数Foo.getName,然后执行Foo.getName,再然后new。】
new Foo.getName();
new Foo().getName();
// (1)new Foo():实例;(2)new Foo().getName:就是函数function () {console.log(3)}; (3)new 函数():执行这个函数,输出3,返回实例
new new Foo().getName();

1594476321466

1594476338366
1594476359760

练习题6

// 有些简单函数就不用再画开辟堆保存函数的图
function Fn() {
    let a = 1;
    this.a = a;
}
Fn.prototype.say = function () {
    this.a = 2;
}
Fn.prototype = new Fn;
let f1 = new Fn;

Fn.prototype.b = function () {
    this.a = 3;
};
console.log(f1.a);
console.log(f1.prototype);
console.log(f1.b);
console.log(f1.hasOwnProperty('b'));
console.log('b' in f1);
console.log(f1.constructor == Fn);

posted on 2020-10-23 10:59  冲啊!  阅读(230)  评论(0编辑  收藏  举报

导航