深入理解JavaScript原型链
JavaScript 是一种基于原型的语言。理解原型和原型链对于掌握 JavaScript 的继承机制和对象模型至关重要。在本文中,我们将深入探讨 JavaScript 原型链的概念及其在实际编程中的应用。
1.什么是原型
在 JavaScript 中,每个对象都有一个与之关联的原型对象。这个原型对象也是一个对象,它可以拥有自己的原型,以此类推,形成一个链条,这就是所谓的原型链。
2.构造函数和原型
1 2 3 4 5 6 7 8 9 10 11 12 13
| function Person(name) { this.name = name; }
Person.prototype.sayHello = function() { console.log('Hello, my name is ' + this.name); };
let person1 = new Person('Alice'); let person2 = new Person('Bob');
person1.sayHello(); person2.sayHello();
|
在上述的实例中Person是一个构造函数,Person.prototype是Person构造函数的原型对象,sayHello方法被添加到Person.prototype上,这意味着所有 Person 的实例都可以访问这个方法。
3.property和__proto__属性
在JavaScript中,对象属性分为两类:自身属性和原型链属性。
自身属性是直接定义在对象自身上的属性,原型属性是通过原型链继承的属性。
1 2 3 4 5 6 7 8
| const person = { name: "Alice", age: 30 };
console.log(person.hasOwnProperty('name')); console.log(person.hasOwnProperty('age')); console.log(person.hasOwnProperty('toString'));
|
__proto__属性是每一个对象都有的一个内部属性,它指向对象的原型(即构造函数的 prototype 属性)。
4.原型链
当我们访问对象的属性或方法时,JavaScript 引擎会首先在对象自身的属性中查找。如果找不到,则会沿着原型链向上查找,直到找到属性或到达原型链的顶端(Object.prototype),其 proto 为 null。
1 2 3
| console.log(person1.__proto__ === Person.prototype); console.log(Person.prototype.__proto__ === Object.prototype); console.log(Object.prototype.__proto__ === null);
|
原型链检测
在我们扣代码时通常会遇到一些环境检测,环境检测通过才会返回正确的结果,但我们在补环境时通常不会按照实际浏览器的原型链去补,下面是常用的补环境方式:
1 2
| navightor = {} navightor.userAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36 Edg/127.0.0.0'
|
当js代码中存在原型链检测,这种补环境的方式是无法通过的,比如:
1 2 3 4 5 6 7 8
| navightor = {} navightor.userAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36 Edg/127.0.0.0' if (Object.hasOwnProperty(navigator, 'userAgent')) { return '错误的结果' } else { return '正确的结果' }
|
上面这种常规补环境的方式如果遇到原型链的检测那无法得到正确的结果,我们必须符合浏览器的环境,所以我们要将对应的属性补在原型链上,如下是补环境的方式符合浏览器的真实环境:
1 2 3 4 5 6 7
| Navigator = function () { } Navigator.prototype = { userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36 Edg/127.0.0.0' } navigator = new Navigator()
|
描述符检测
Object.getOwnPropertyDescriptor()
方法返回指定对象上一个自有属性对应的属性描述符。(自有属性指的是直接赋予该对象的属性,不需要从原型链上进行查找的属性)
1 2 3 4 5 6 7 8 9
| Object.getOwnPropertyDescriptor(navigator,'userAgent')
var navigator = { userAgent:"aaaaa" } Object.getOwnPropertyDescriptor(navigator,'userAgent')
|
value:与属性关联的值(仅限数据描述符)。
writable:当且仅当与属性关联的值可以更改时,为 true
(仅限数据描述符)
configurable:当且仅当此属性描述符的类型可以更改且该属性可以从相应对象中删除时,为 true
。
enumerable:当且仅当此属性在相应对象的属性枚举中出现时,为 true
。
以下是检测描述符检测的正确补法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| navigator = {} navigator.__proto__.userAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36'
console.log(navigator.userAgent); console.log(Object.getOwnPropertyDescriptor(navigator, 'userAgent'));
var Navigator = function() {}; Navigator.prototype = {"userAgent": "123123123"}; navigator = new Navigator(); console.log(navigator.userAgent) console.log(Object.getOwnPropertyDescriptor(navigator, 'userAgent'));
Document = function Document(){}
Object.defineProperty(Document.prototype,'createElement',{ configurable: true, enumerable: true, value: function createElement(){}, writable: true, }) HTMLDocument = function HTMLDocument(){}
Object.setPrototypeOf(HTMLDocument.prototype,Document.prototype) document = new HTMLDocument()
console.log(Object.getOwnPropertyDescriptor(document.__proto__.__proto__, 'createElement'));
|
toString检测
在 JavaScript 中,toString()
是一个内置函数,用于将一个值转换为字符串。
toString()
方法可以应用于大部分 JavaScript 值类型,包括数字、布尔值、对象、数组等。它的返回值是表示该值的字符串。
如果js代码中检测toSting方法,那么即使我们按照原型链的方式去补环境也无法通过检测
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| Document = function Document(){} Object.defineProperty(Document.prototype,'createElement',{ configurable: true, enumerable: true, value: function createElement(){}, writable: true, }) HTMLDocument = function HTMLDocument(){} Object.setPrototypeOf(HTMLDocument.prototype,Document.prototype) document = new HTMLDocument()
console.log(document.toString()); --> 输出function createElement(){}
Document = function Document(){} Object.defineProperty(Document.prototype,'createElement',{ configurable: true, enumerable: true, value: function createElement(){}, writable: true, })
HTMLDocument = function HTMLDocument(){} Object.setPrototypeOf(HTMLDocument.prototype,Document.prototype) document = new HTMLDocument() document.createElement.toString = function(){return "function createElement() { [native code] }"} console.log(document.createElement.toString());
|