1.创建对象的几种方式
1.对象字面量模式
直接使用{}
定义键值对:
const obj = { key: 'value' };
2.Object()
构造函数模式
使用内置构造函数(较少使用):
const person = new Object();
console.log(person)//输出 {}
3.构造函数模式
通过函数模拟类,结合new
关键字创建实例:
function Person(name) { this.name = name; }
const person = new Person('John');
console.log(person)//输出{name:"join"}
2.原型与原型链
1.构造函数
实例有一个属性
constructor
指向构造函数,实例拥有构造函数内部定义的属性和方法,构造函数相当于一个模板蓝图
function User(){
this.age = 18
this.login = function (){
console.log("登录成功")
}
}
user1 = new User()
user1 = new User()
console.log(user1.age) //18
console.log(user1.constructor == user2.constructor) //true
2.显式原型
在js中,函数对象有一个特殊的属性叫做
显式原型(prototype)
,它指向另一个对象,这个对象(User.prototype)被称为原型对象
原型对象是用来共享属性和方法
我们可以看下面的代码
- 在构造函数内部定义方法
function User(){
this.age = 18
this.login = function (){
console.log("登录成功")
}
}
user1 = new User()
user2 = new User()
console.log(user1)// { age: 18, login: [Function (anonymous)] }
console.log(user1.age == user2.age) //true
console.log(user1.login == user2.login) //false 因为指向了不同内存地址
每个实例在堆内存中都有自己独立的方法。上面代码中,创建2个实例就会创建2个login
方法。
- 在原型上定义方法
function User(){
this.age = 18
}
User.prototype.login = function(){ console.log("登录成功")}
user1 = new User()
user2 = new User()
console.log(user1)//{ age: 18 }
console.log(user1.login == user2.login)//true
console.log(user1.login())//登录成功
创建出来的实例对象上本身并没有login
方法
这个login
方法在内存中只存在一份,它被分配在堆内存中,并且挂载到User.prototype
这个对象上,如果在当前对象中没有获取到就会去它的原型上面获取。
- 二者比较
特性 | 定义在原型上 User.prototype.login= function() | 定义在构造函数内部 this.login = function() |
---|---|---|
方法存储位置 | 原型对象(仅存一份) | 每个实例独立存储(每次new 都创建新函数) |
内存占用 | ✅ 极低 (1000个实例共享1个方法) | ❌ 爆炸式增长 (1000个实例创建1000个独立方法) |
3.隐式原型
在js中,每个对象(除Object.create(null) 创建外)都有一个
__proto__
属性(左右两个短下划线),这个被称为隐式原型
,
隐式原型是js中所有对象的核心继承机制,直接决定了原型链的运作方式,__proto__
存在的意义在于为原型链查找提供方向。隐式原型指向该对象的构造函数原型对象(即User.prototype
)但通过 Object.create()
创建的对象会直接指向参数对象。
1.通过构造函数创建的实例对象,实例对象的隐式原型指向构造函数的显式原型
function User(){this.age = 18}
User.prototype.login = function(){ console.log("登录成功")}
user1 = new User()
console.log(user.__proto__ == User.prototype)//true
2.通过Object.create()创建的实例对象,隐式原型指向参数对象
const animal = { type: 'unknown' };
// 参数对象 = animal
const dog = Object.create(animal);
// dog.__proto__ 直接指向 animal(而非默认的 Object.prototype)
console.log(dog.type); // 'unknown' ✅
console.log(dog.__proto__ === animal); // true ✅
- 通过手动设置的原型实现继承
我们知道,从一个对象上获取属性,如果在当前对象中没有获取到就会去它的原型上面获取:
//user1.__proto__ = User.prototype
var obj1 = {age:"18"}
var obj = {name:"john"};
obj.__proto__ = obj1
console.log(obj.name)//john
console.log(obj.age)//"18"
构造函数 、实例、原型对象之间的关系图
4.原型链
原型链(Prototype Chain)是JavaScript中实现继承的机制。
每个对象都有一个原型(__proto__属性),而原型本身也是一个对象,它也有自己的原型,这样一层一层形成的链式结构就是原型链。当访问一个对象的属性或方法时,如果对象自身没有该属性,就会沿着原型链向上查找,直到找到该属性或到达原型链的顶端(null)为止。
function Person() {}
const p = new Person();
// 原型链验证
console.log(p.__proto__ == Person.prototype); // L1 true
console.log(Person.prototype.__proto__ == Object.prototype);// L2 true
console.log(Object.prototype.__proto__ == null); // L3 true
原型继承关系图
注:图中涉及到的部分知识点文中未提及
3.结语
本文旨在简单的理解原型和原型链来对抗环境检测和还原关键加密逻辑,实际场景中网站会通过检测原生对象原型链来判断是否处于浏览器环境。
比如某网站会检查navigator.plugins的原型链长度判断是否爬虫;
用自定义Error对象,捕获错误后检查stack属性是否包含原生Error原型的方法名等等;
如需进一步理解原型链可参考文章:
参考文章