📢 大家好,我是 【战神刘玉栋】,有10多年的研发经验,致力于前后端技术栈的知识沉淀和传播。 💗
🌻 近期刚转战 CSDN,会严格把控文章质量,绝不滥竽充数,欢迎多多交流。👍
文章目录
写在前面的话
又回到前端系列了,之前写后端和其他内容去了,进度落下,赶紧补一下。
本篇文章介绍一下 JavaScript 里面的一些经典知识点,先以prototype
原型链开篇。
让我们开始!
快速了解
【概念说明】
在JavaScript
中,每个对象都有一个原型prototype
属性,它指向另一个对象。这个被指向的对象也有自己的原型,以此类推,最终形成了一个原型链。原型链的顶端是Object.prototype,它是所有对象的根原型。
当我们访问一个对象的属性时,如果该对象自身没有这个属性,JavaScript会沿着原型链向上查找,直到找到匹配的属性或者到达原型链的末端。
【用代码说话】
直接上一段示例代码:
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.sayHello = function() {
console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
};
const alice = new Person('Alice', 20);
alice.sayHello(); // 输出:Hello, my name is Alice and I am 20 years old.
浏览器运行该代码,F12控制台查看alice
对象,如下图所示:
分析一下上图的这些内容:
- alice 对象包含 age 和 name 两个属性和一个原型对象
- 原型对象可以通过 alice.proto 访问,或 Person.prototype,两者是相等的
- 原型对象的再上一次层指向 Object.prototype,再往上一层就是 null
- 对,没有错,你看到了一个类似链式调用的东西,这是原型链
Tips:学习前端,特别是JavaScript,直接运行Demo,F12查看效果是一个快速学习方式。
知识介绍
prototype 与原型链
在 JavaScript 中,prototype 是用于实现继承和共享属性与方法的一种机制。每一个 JavaScript 对象(除了 null)在创建时都与另一个对象(称为其 “prototype”)相关联。这个 “prototype” 对象本身也有一个 “prototype”,这样一直到一个对象的 prototype 为 null 为止,这形成了一个链条,被称为 “原型链”(prototype chain)。
关于 prototype 继承
所有的 JavaScript 对象都会从一个 prototype(原型对象)中继承属性和方法:
Date 对象从 Date.prototype 继承。
Array 对象从 Array.prototype 继承。
Person 对象从 Person.prototype 继承。
所有 JavaScript 中的对象都是位于原型链顶端的 Object 的实例。
JavaScript 对象有一个指向一个原型对象的链。当试图访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型,以及该对象的原型的原型,依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾。
Date 对象、Array 对象、以及 Person 对象从 Object.prototype 继承。
console.log(alice.__proto__ === Person.prototype); // 输出:true
console.log(Person.prototype.__proto__ === Object.prototype); // 输出:true
console.log(Object.prototype.__proto__ === null); // 输出:true
使用 prototype 添加属性
JavaScript 对象要添加新的属性或方法,很简单。
有点像 Java 的Map那边随意,直接追加即可,类似 alice.xxx = ‘1111’
但如果想追加的属性是和其他对象,继承和共享的,那就可以用到 prototype 方式。
Person.prototype.nationality = "English";
console.log(alice.__proto__.nationality);
使用 prototype 属性就可以给对象的构造函数添加新的属性和方法。
关键词 constructor
前面示例一定看到了原型对象上有一个 constructor 属性,这是何物?
其实就是和 Java 中的构造函数类似的概念,用下面代码可以看出来,原型的 constructor就是指向类本身。
alice.__proto__.constructor === Person // true
结合前面说的内容, 整个概念是否绕来绕去,找了下面这张图,感觉能比较直观了看明白之间的调用关系。
几个概念复述一下:
- prototype 是 JavaScript 中实现继承和共享属性与方法的基础。
- 构造函数的 prototype 属性用于定义所有实例共享的方法和属性。
- 每个对象都有一个 proto 属性,对象通过该属性引用其原型,形成原型链。
- 每个函数在定义时会自动获得一个 prototype 属性,这个属性是一个对象,包含一个 constructor 属性指向函数本身。
- 当访问对象的一个属性或方法时,JavaScript 会首先在对象本身查找。如果没有找到,它会沿着原型链向上查找,直到找到或到达链的末端(即 null)。
Tips:理解 prototype 和原型链是掌握 JavaScript 面向对象编程的关键。通过这种机制,可以实现代码的重用和更高效的内存使用。
原型链的性能和优化
原型链在JavaScript中的运作会带来一定的性能开销。在访问属性时,查找过程需要沿着原型链逐级查找,直到找到属性或者到达原型链末端。因此,过深的原型链结构可能导致性能下降。为了优化性能,可以合理设计对象的原型链,避免过于庞大和复杂的结构。
Tips:简单来说,就是不要太复杂,但一般前端设计也不应该很重,最多父子类的结构了,损耗可以忽略不计。
Java 和 JavaScript 面向对象
JavaScript 和 Java 都是面向对象编程语言,但它们在类和对象的实现和使用上有一些显著的差异。以下是对 JavaScript 中类和对象的介绍,并通过 Java 的概念进行对比说明。
类:
JavaScript ES6 引入类,使得类定义更加简洁和直观,但其底层依然基于原型链。
Java 中类是核心概念,定义了对象的属性和行为。
对象:
JavaScript 对象是键值对的集合,可以动态修改。
Java 对象是类的实例,属性和方法在类中定义。
继承:
JavaScript 通过 extends 关键字实现类的继承,使用 super 调用父类的构造函数和方法。
Java 通过 extends 关键字实现类的继承,使用 super 关键字调用父类的构造函数和方法。
总结:
尽管 JavaScript 和 Java 在语法和特性上有很多不同,但它们都支持面向对象编程的基本概念,如类、对象和继承。JavaScript 的类和对象模型更加灵活和动态,而 Java 的类和对象模型更为静态和严格。
用法拓展
创建对象的N种方式
对于 Java 后端程序猿而言,类与对象不是 Java 面向对象的概念吗,前端怎么也有?
其实 JavaScript 已经不是后端眼中随意简单的脚本语言了,Java 有的概念它也有,Java 能做的很多事情,它也能做。
我们有多种方式可以创建对象,根据不同的创建方式,JavaScript对象的原型链也会有所不同。
构造函数方式
使用构造函数方式创建对象的原型是构造函数的原型对象(prototype)。
这个前面章节都在介绍这种方式,示例代码再贴一下?
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.sayHello = function() {
console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
};
const alice = new Person('Alice', 20);
alice.sayHello(); // 输出:Hello, my name is Alice and I am 20 years old.
console.log(alice.__proto__ === Person.prototype); // 输出:true
字面量方式创建对象
使用字面量方式创建对象的原型是 Object.prototype,即所有字面量创建的对象都是 Object 的实例。
const lwObj = {
name: '战神',
age: 32,
sayHello() {
console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
}
};
console.log(lwObj.__proto__ === Object.prototype);// 输出:true
Class 方式创建对象
在 ES6 之前,JavaScript 没有类的概念,开发者主要通过构造函数和原型链来实现面向对象编程。ES6 引入了类的语法,使得定义和继承类更加直观和类似于其他面向对象语言(如 Java)。
Class 方式创建的对象和构造函数方式创建的对象一样,其原型是Class构造函数的prototype属性所指向的对象。
Tips:这也是推荐方式,不过一般复杂的前端逻辑才会去专门设计和定义类。
class PersonClass {
constructor(name, age) {
this.name = name;
this.age = age;
}
sayHello() {
console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
}
}
const hanMei = new PersonClass('HanMei', 25);
hanMei.sayHello(); // 输出:Hello, my name is HanMei and I am 25 years old.
console.log(hanMei.__proto__ === PersonClass.prototype); // 输出:true
prototype 实现继承
直接看下面的代码,就是基于原型链的特性。
// 定义父类函数
function Company(){
this.boss = 'lw';
this.name = 'cjwmy';
this.show = function(){
return this.boss + ',' + this.name;
}
}
// 定义子类函数
function Zysoft(){
this.soft = ['zyhip','zycomm'];
}
// 绑定原型链
Zysoft.prototype = new Company();
let one = new Zysoft();
console.log(one.show()); //lw,cjwmy
扩展原有对象的方法 Demo
首先,我们要先了解一下类的概念,JavaScript 本身是一种面向对象的语言,它所涉及的元素根据其属性的不同都依附于某一个特定的类。我们所常见的类包括:数组变量(Array)、逻辑变量(Boolean)、日期变量(Date)、结构变量(Function)、数值变量(Number)、对象变量(Object)、字符串变量(String) 等,而相关的类的方法,也是程序员经常用到的(在这里要区分一下类的注意和属性发方法),例如数组的push方法、日期的get系列方法、字符串的split方法等等,但是在实际的编程过程中不知道有没有感觉到现有方法的不足?prototype 方法应运而生!
【最简单的例子,自己写add等方法,了解 prototype】
是不是很简单?这一节仅仅是告诉读者又这么一种方法,这种方法是这样运用的。
(1) Number.add(num):作用,数字相加
实现方法:Number.prototype.add = function(num){return(this+num);}
试验:alert((3).add(15)) -> 显示 18
(2) Boolean.rev(): 作用,布尔变量取反
实现方法:Boolean.prototype.rev = function(){return(!this);}
试验:alert((true).rev()) -> 显示 false
(3)测试demo
String.prototype.test = function(value) {
alert(1)
};
【已有方法的实现和增强,初识 prototype】
(1) Array.push(new_element)
//作用:在数组末尾加入一个新的元素
Array.prototype.push = function(new_element){
this[this.length]=new_element;
return this.length;
}
//让我们进一步来增强他,让他可以一次增加多个元素!
Array.prototype.pushPro = function() {
var currentLength = this.length;
for (var i = 0; i < arguments.length; i++) {
this[currentLength + i] = arguments[i];
}
return this.length;
}
(2)String.length
//作用:这实际上是 String 类的一个属性,但是由于 JavaScript 将全角、半角均视为是一个字符,
//在一些实际运用中可能会造成一定的问题,现在我们通过 prototype 来弥补这部不足。
String.prototype.cnLength = function(){
var arr=this.match(/[^\x00-\xff]/ig);
return this.length+(arr==null?0:arr.length);
}
alert("EaseWe空间Spaces".cnLength()) -> 显示 16
【新功能的实现,深入 prototype】
在实际编程中所用到的肯定不只是已有方法的增强,更多的实行的功能的要求。
(1) String.left()
//问题:用过 vb 的应该都知道left函数,从字符串左边取 n 个字符,但是不足是将全角、半角均视为是一个字符,造成在中英文混排的版面中不能截取等长的字符串
//作用:从字符串左边截取 n 个字符,并支持全角半角字符的区分
String.prototype.left = function(num,mode){
if(!/\d+/.test(num))return(this);
var str = this.substr(0,num);
if(!mode) return str;
var n = str.Tlength() - str.length;
num = num - parseInt(n/2);
return this.substr(0,num);
}
alert("EaseWe空间Spaces".left(8)) -> 显示 EaseWe空间
alert("EaseWe空间Spaces".left(8,true)) -> 显示 EaseWe空
//本方法用到了上面所提到的String.Tlength()方法,自定义方法之间也能组合出一些不错的新方法呀!
(2) Date.DayDiff()
作用:计算出两个日期型变量的间隔时间(年、月、日、周)
Date.prototype.DayDiff = function(cDate,mode){
try{
cDate.getYear();
}catch(e){
return(0);
}
var base =60*60*24*1000;
var result = Math.abs(this - cDate);
switch(mode){
case "y":
result/=base*365;
break;
case "m":
result/=base*365/12;
break;
case "w":
result/=base*7;
break;
default:
result/=base;
break;
}
return(Math.floor(result));
}
alert((new Date()).DayDiff((new Date(2002,0,1)))) -> 显示 329
alert((new Date()).DayDiff((new Date(2002,0,1)),"m")) -> 显示 10
//当然,也可以进一步扩充,得出响应的小时、分钟,甚至是秒。
(3) Number.fact()
//作用:某一数字的阶乘
Number.prototype.fact=function(){
var num = Math.floor(this);
if(num<0)return NaN;
if(num==0 || num==1)
return 1;
else
return (num*(num-1).fact());
}
alert((4).fact()) -> 显示 24
//这个方法主要是说明了递归的方法在 prototype 方法中也是可行的!
(4) Array.indexof()
//下面是给js中的Array加的一个方法(本来js中Array是没有返回下标的方法的)
Array.prototype.indexof = function(value) {
var a = this;
for (var i = 0; i < a.length; i++) {
if (a[i] == value)
return i;
}
}
var arr = ['1', '2', '3', '4', '5', '6', '7'];
alert(arr.indexof('3'));
prototype 基础测试 Demo
本章节,是博主早期针对 prototype 做的一些测试代码,可以跳过。
===== 第一例,prototype初使用--为类型增加额外的方法和属性
Object.prototype.Property = 1;
Object.prototype.Method = function (){
alert(1);
}
var obj = new Object();
alert(obj.Property);
obj.Method();
结论:可以在类型上使用proptotype来为类型添加行为,这些行为只能在类型的实例上体现,并且在增加之前实例化的对象也享有这些变化。
规则1:JS中允许的内置类型有Array, Boolean, Date, Enumerator, Error, Function, Number, Object, RegExp, String;
规则2:直接用Object.Method()也可以调用(但一般不这么使用),因为Object是所有对象(包括函数对象)的基础,其他内置类型是不可以。
===== 第二例,在实例对象上不能使用prototype,否则发生编译错误
var obj = new Object();
obj.prototype.Property = 1; //Error
obj.prototype.Method = function() //Error
{
alert(1);
}
===== 第三例,直接给类型定义"静态"属性和方法,只能使用类型本身调用,对象无法调用,同Java的static关键字声明的类变量
Object.Property = 1;
Object.Method = function()
{
alert(1);
}
alert(Object.Property);
Object.Method(); //正常调用
var obj = new Object();
alert(obj.Property); //Error实例不能调用类型的静态属性或方法,否则发生对象未定义的错误。
===== 第四例,演示通常在JavaScript中定义一个自定义类型的方法
function Aclass()
{
this.Property = 1;
this.Method = function()
{
alert(1);
}
}
var obj = new Aclass();
alert(obj.Property);
obj.Method();
===== 第五例,接第四例,可以在外部使用prototype为自定义的类型添加属性和方法。
Aclass.prototype.Property2 = 2;
Aclass.prototype.Method2 = function
{
alert(2);
}
var obj = new Aclass();
alert(obj.Property2);
obj.Method2();
===== 第六例,接第四例,在外部不能通过prototype改变自定义类型的属性或方法,不会报错但改变无效;
但如果属性本身就是通过prototype扩展的,则可以继续用prototype修改。
Aclass.prototype.Property = 2;
Aclass.prototype.Method = function
{
alert(2);
}
var obj = new Aclass();
alert(obj.Property); //仍然弹出1
obj.Method();
===== 第七例,接第四例,可以在对象上改变属性。(这个是肯定的)
var obj = new Aclass();
obj.Property = 2;
obj.Method = function()
{
alert(2);
}
alert(obj.Property);
obj.Method();
也可以在对象上改变方法。(和普遍的面向对象的概念不同)
===== 第八例,接第四例,可以在对象上增加属性或方法
var obj = new Aclass();
obj.Property2 = 2;
obj.Method2 = function()
{
alert(2);
}
alert(obj.Property2);
obj.Method2();
===== 第九例,接第四例,这个例子说明了一个类型如何从另一个类型继承。
function AClass2()
{
this.Property2 = 2;
this.Method2 = function()
{
alert(2);
}
}
AClass2.prototype = new AClass();
var obj = new AClass2();
alert(obj.Property);
obj.Method();
alert(obj.Property2);
obj.Method2();
===== 第十例,接上例, 这个例子说明了子类如何重写父类的属性或方法。但不能改变自身的属性和方法。
AClass2.prototype = new AClass();()
AClass2.prototype.Property = 3;
AClass2.prototype.Method = function()
{
alert(4);
}
var obj = new AClass2();
alert(obj.Property);
obj.Method();
可见JavaScript能够实现的面向对象的特征有:·公有属性(public field)·公有方法(public Method)·私有属性(private field)·私有方法(private field)·方法重载(method overload)·构造函数(constructor)·事件(event)·单一继承(single inherit)·子类重写父类的属性或方法(override)·静态属性或方法(static member)
总结陈词
本篇文章分享一下 JS 世界中prototype
的用法,希望能帮助到大家。
后端程序猿如果想往全栈工程狮发展,那JavaScript
必须和Java
玩得一样明白,接下来系列文章,带你一起探索,无所不能的JavaScript
。
💗 后续会逐步分享企业实际开发中的实战经验,有需要交流的可以联系博主。