什么是面向对象?
对象:万物皆对象
类:对对象的归类和细分
实例:类中的具体成员
-
JS内置类:
- 每一种内置的数据类型都有自己对应的内置类:Number、String、Object等。代码中的具体值就是对应的类的实例。例如10 -> Number -> Object
- 每一个HTML元素标签:包括window/document等在JS中都有自己的内置类。例如div -> HTMLDivElement -> HTMLElement -> Element -> Node -> EventTarget -> Object
instanceof 检测某个实例是否属于这个类。局限性:不能识别原始值。
类型转换存在内部处理机制拆箱和装箱
console.log(10.toFixed(2)) // 10.00 10 -> Object(10)
console.log(new Number(10) + 10) // 20 Symbol.toPrimitive / valueOf / toString
hasOwnProperty 检测某个属性是否是这个对象的私有属性
var obj = {age: 20};
obj.hasOwnProperty('age'); // true
obj.hasOwnProperty('hasOwnProperty'); // false
in操作符 检测检测某个属性是否是这个对象的属性(无论私有还是公有)
对象循环 for in
for in 的缺陷
- 不能迭代Symbol属性
- 不一定按照自己编写的键值对顺序迭代(优先迭代数字属性,从小到大,再去迭代非数字属性「自己编写的顺序」)
- 不仅会迭代对象的私有属性,对于一些自己扩展的公有属性也会迭代到「迭代可枚举的」
// 遍历数组 for循环 比 for in 循环好
let arr = [10, 20 ,30];
/**
for 循环的本质不是遍历数组,是自己控制一个循环的逻辑,只不过每一轮循环的值恰好和数组索引类似,可以直接作为下标使用,所以看起来是遍历数组。
*/
for (let i = 0; i < arr.length; i++) {
console.log(arr[i], i);
}
// for in 本质是迭代对象,按照本身的键值对去一一迭代的
for (let key in arr) {
console.log(arr[key], key);
}
Object.getOwnPropertySymbols 获取对象所有Symbols的属性
Object.keys || Object.getOwnPropertyNames 拿到所有非Symbol的私有属性
对象的属性类型只有两种 string || Symbol
原型和原型链
-
大部分函数数据类型的值,都具备 prototype 属性,这个属性被称为原型属性,也称为显式原型。属性值本身是一个对象(浏览器会默认为其开辟一个堆内存,用来存储当前类所属实例可以调用的公共属性和方法),在浏览器默认开辟的这个堆内存中「原型对象」,有一个默认的属性
constructor(构造器/构造器)
,属性值是当前类本身。-
函数数据类型有哪些?
- 普通函数(实名函数、匿名函数)
- 箭头函数
- 构造函数/类
- 生成器函数
- ......
-
不具备prototype属性的函数类型
- 箭头函数(只有
__proto__
) - 基于ES6给对象成员赋值函数的快捷操作
let obj = { fn() {} }
- ......
- 箭头函数(只有
-
-
每一个对象数据类型都具备
__proto__
(原型链/隐式原型),属性值指向自己所属类的原型prototype-
有哪些对象数据类型?
- 普通对象
- 特殊对象,Array、Date、Math、Error等
- 函数对象
- 实例对象
- 构造函数的prototype
- ......
-
let arr = [1, 2, 3];
// arr 是 Array 类的实例,所以arr.__proto__ => Array.prototype;
// Array 原型的值也是一个对象,拥有自己的__proto__ 并指向Object.prototype
// Array.prototype.__proto__ => Object.prototype
// 而Object是所有类的基类,所以Object.prototype.__proto__ 指向的也是Object自己,这样没有意义,所以Object.prototype.__proto__ 的值为null
// 构造函数原型的方法相对于实例来说是公有的,相对于本身来说 是私有的方法
arr.hasOwnProperty('forEach') // false
Array.prototype.hasOwnProperty('forEach') // true
实现new
function new() {
const Func = [].shift.call(arguments);
let obj, result, proto = Func.prototype;
if (Func === Symbol || Func === BigInt || typeof Func !== 'function' || !proto) {
throw new TypeError(`${Func} is not constructor`);
}
obj = Object.create(proto);
result = Func.apply(obj, arguments);
if (typeof result === 'object') {
return result;
}
return obj;
}
实现Object.create
Object.create = function create(prototype) { // 将参数作为创建的新对象的原型
if (prototype !== null && typeof prototype !== 'object') {
throw new TypeError(`${prototype} is Not Object or null`);
}
function _Proxy() {
}
_Proxy.prototype = prototype;
return new _Proxy;
}
原型重定向
内置类原型不允许重定向,但可以对单一的属性或方法进行重写。
方法中的属性赋值都是赋值给私有的属性。
函数的多种角色
-
函数
- 普通函数(上下文、作用域)
- 构造函数(类、实例、原型、原型链)
-
普通对象(键值对)
function Fn() {};
Fn.__proto__ === Function.prototype; // true
Function.__proto__ === Function.prototype; // true
Object.__proto__ === Function.prototype; // true
Object.__proto__.__proto__ = Object.prototype; // true
Object instanceof Function // true
Function instanceof Object // true
Function.prototype // ƒ () { [native code] }
所有对象(包含函数对象 )都是Object的实例,都可以调用Object原型的方法。
JS中所有的非基础类型的值都是对象的实例,但是函数比较特殊,即是对象,也是函数。
Function.prototype 是一个匿名空函数,但是没有prototype,所以还是把它当做和其他原型对象一样即可。