面向对象进阶知识

41 阅读2分钟

对象公有属性的检测

实现 hasPubProperty:用于检测对象是否有某个公有属性,注意不管私有是否存在这个方法,只要原型链上有,则当前属性就是对象的公有属性。

// 前置知识
//   @1:__proto__ 在 IE 无法访问,获取原型对象得用 Object.getPrototypeOf(obj)
//   @2:hasOwnProperty 可以拥有检测对象自身是否包含某属性,和 'name' in obj 用法一致
Object.prototype.hasPubProperty = function(key) {
	// this -> 实例对象 
	// 获取原型对象
	let proto = Object.getPrototypeOf(this);

	while(proto) {
		if (proto.hasOwnProperty(key)) return true;

		proto = Object.getPrototypeOf(proto); // 继续往上查找
	}

	return false;
}

let obj = {
	age: 18,
	toString() {}
}

console.log(obj.hasPubProperty('age'));
console.log(obj.hasPubProperty('toString'));

重写 new

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 dog = new Dog('dog\'s god');

dog.sayName();
dog.bark();

我们来手动实现一个简版 new

// 前置知识
//   @1 new 会默认创建一个实例对象,并创建 __proto__ 属性指向构造函数原型链
//   @2 构造函数返回结果如果是原始值,则返回默认创建的实例对象,否则返回函数返回值。
//   @3 实例对象没有自己的 constructor,他访问的是 __proto__ 中的 constructor
//   @4 如果一个对象没有 __proto__ 属性,手动添加也无效
//   @5 构造函数无 prototype 报错(箭头函数和对象内 es6 语法声明的函数)
//   @6 Symbol 和 BigInt 不允许被 new 调用,但是它们是函数哦,typeof -> 'function'
function _new(func, ...params) {
	// 不是函数
	if (typeof func !== 'function') {
		throw new TypeError(`func is not a constructor!`);
	}

	// Symbol 或 BigInt
	let name = func.name;
	if (name == 'Symbol' || name == 'BigInt') {
		throw new TypeError(`${ name } is not a constructor!`);
	}

	// 没有原型对象
	if (!func.prototype) {
		throw new TypeError(`${ func } is not a constructor!`);
	}

	// 所以此处不能 Object.create(null) 参考前置知识 4
	// 不过可以一步到位 下面两步等价于 let instance = Object.create(func.prototype)
	let instance = {}; 

	// 注意这行代码的位置 不能给 result 上也挂这个方法 遵从函数内部返回的对象
	instance.__proto__ = func.prototype; 

	let result = func.call(instance, ...params);
	let valueType = typeof result;

	// 函数返回值是不是对象类型的值
	//   null 或者 非对象
	if (String(valueType) === 'null' ||
	 	(valueType != 'object') && valueType != 'function') {
		result = instance;
	} 

	return result;
}

let dog = _new(Dog, 'dog\'s god');

dog.sayName();
dog.bark();

一道阿里面试题

// 前置知识
//   @1 new 操作符「带参数」优先级是 20,不带参数优先级 19
//   @2 成员访问点操作符优先级 20
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(); // 2
getName(); // 4
Foo().getName(); // 1  -> Foo() 执行后 全局的 window.getName() -> 1
new Foo.getName(); // 2 -> 先执行 Foo.getName() 再 new
new Foo().getName(); // 3 -> 先执行 new Foo()

// 同样操作符连续操作 先右后左
// (参考 typeof typeof typeof '123'),假设得到的实例是 instance
// new instance.getName() -> 点操作符先执行,假设得到的方法叫 fn 
// new fn(); -> 输出 3
new new Foo().getName(); // 3 

图解执行过程: