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:给当前这个类执行时候传递的实参
- 拥有普通函数执行的一面,让他作为普通函数执行
- 创建一个实例对象
-> 空对象
-> 对象.proto===类.prototype- 方法执行的时候,方法中的this是实例对象
- 判断方法的返回结果,如果返回的不是引用类型值,默认把实例返回
Object.create(xxx):创建一个空对象,并且让把xxx作为创建对象的原型(空对象.
__proto __
= xxx),xxx必须是对象或者null,如果xxx是null,则创建一个没有任何原型指向的空对象
我的版本
- 在构造函数代码开始执行之前,创建一个空对象;
- 设置新对象的__proto__指向构造函数的prototype对象,p.proto = XXXX.prototype;
- 执行构造函数,修改this的指向,让构造函数中的this指向创建出来的空对象,拷贝构造函数中的方法、属性给新对象;
- 判断构造函数返回的结果,如果返回的不是引用类型值,默认把实例返回;如果返回的是引用类型值,则返回引用类型。
// 有些简单函数就不用再画开辟堆保存函数的图
// 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();
练习题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);