首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

JavaScript 面向對象進階:原型鏈、繼承模式與拖拽實戰完整指南

     編輯:Java 綜合教學

JavaScript 面向對象進階:原型鏈、繼承模式與拖拽實戰完整指南

一、JS 原型本質

JS 是基於原型的語言,無類概念 (ES6 class 只是語法糖)。所有對象繼承自 Object.prototype,形成原型鏈。

系統對象也是原型模式

// 內部實現類似
function Array() { this.length = 0; }
Array.prototype.push = function() { ... };
Array.prototype.sort = function() { ... };

const arr = new Array();
arr.push(1); // 通過原型鏈找到 push

警告:切勿修改內置原型方法,會污染全局環境

包裝對象機制

基本類型 string/number/boolean 訪問屬性/方法時,臨時創建對應包裝對象 (String/Number/Boolean),操作完成即銷毀

const str = 'hello';
str.charAt(0); // 相當於 new String('hello').charAt(0)
// 擴展原型影響所有基本類型值
String.prototype.lastChar = function() { return this[this.length-1]; };
console.log('abc'.lastChar()); // 'c'

二、原型鏈核心機制

  • 每個對象有隱式屬性 __proto__ 指向構造函數的 prototype
  • 查找屬性/方法:自身 → \_\_proto\_\_ → \_\_proto\_\_.\_\_proto\_\_ ... → Object.prototype → null
  • constructor 屬性指向構造函數,原型對象默認擁有
function Aaa() { this.num = 20; }
Aaa.prototype.num = 10;
const a1 = new Aaa();
console.log(a1.num); // 20 (自身屬性遮蔽原型)
console.log(a1.__proto__ === Aaa.prototype); // true
console.log(Aaa.prototype.__proto__ === Object.prototype); // true

三、常用檢測方法

iframe 跨域類型判斷:前兩種失效,唯有 Object.prototype.toString.call() 可靠

四、三大繼承模式深度對比

1. 原型鏈繼承

function Super() { this.colors = ['red','blue']; }
Super.prototype.getColors = function() { return this.colors; };

function Sub() {}
Sub.prototype = new Super(); // 關鍵:子原型指向父實例
Sub.prototype.constructor = Sub; // 修正 constructor

const s1 = new Sub(), s2 = new Sub();
s1.colors.push('green');
console.log(s2.colors); // ['red','blue','green'] - 引用類型共享問題!

缺點:引用類型屬性被所有實例共享;無法向父構造函數傳參

2. 構造函數繼承 (借用 call/apply)

function Super(name) { this.name = name; this.colors = ['red']; }
function Sub(name, age) {
    Super.call(this, name); // 在子實例上執行父構造函數
    this.age = age;
}
const s1 = new Sub('Tom', 20), s2 = new Sub('Jerry', 18);
console.log(s1.colors, s2.colors); // 各自獨立 ['red'] ['red']

缺點:方法定義在構造函數內,無法復用;父原型方法不可見

3. 組合繼承 (經典模式) - 推薦

function Super(name) { this.name = name; this.colors = ['red','blue']; }
Super.prototype.sayName = function() { console.log(this.name); };

function Sub(name, age) {
    Super.call(this, name); // 繼承實例屬性 (解決共享問題)
    this.age = age;
}
Sub.prototype = new Super(); // 繼承原型方法
Sub.prototype.constructor = Sub; // 修正 constructor
Sub.prototype.sayAge = function() { console.log(this.age); };

const s1 = new Sub('Tom', 20), s2 = new Sub('Jerry', 18);
s1.colors.push('green');
console.log(s1.colors); // ['red','blue','green']
console.log(s2.colors); // ['red','blue'] - 獨立!
s1.sayName(); s1.sayAge(); // 方法復用正常

4. 拷貝繼承 (jQuery extend 風格)

function extend(target, source) {
    for (let key in source) target[key] = source[key];
}

function Person(name) { this.name = name; }
Person.prototype.sayHi = function() { console.log(this.name); };

function Star(name, job) {
    Person.call(this, name);
    this.job = job;
}
extend(Star.prototype, Person.prototype); // 方法拷貝
Star.prototype.showJob = function() { console.log(this.job); };

五、實戰案例:可拖拽組件與邊界限制繼承

基類:基礎拖拽邏輯

function Drag(id) {
    this.obj = document.getElementById(id);
    this.disX = this.disY = 0;
}
Drag.prototype.init = function() {
    const self = this;
    this.obj.onmousedown = function(e) {
        e = e || window.event;
        self.fnDown(e);
        document.onmousemove = function(e) { self.fnMove(e || window.event); };
        document.onmouseup = self.fnUp;
        return false; // 防止選中文字
    };
};
Drag.prototype.fnDown = function(e) {
    this.disX = e.clientX - this.obj.offsetLeft;
    this.disY = e.clientY - this.obj.offsetTop;
};
Drag.prototype.fnMove = function(e) {
    this.obj.style.left = e.clientX - this.disX + 'px';
    this.obj.style.top = e.clientY - this.disY + 'px';
};
Drag.prototype.fnUp = function() {
    document.onmousemove = document.onmouseup = null;
};

子類:限制邊界內拖拽

function BoundDrag(id) {
    Drag.call(this, id); // 繼承屬性
}
extend(BoundDrag.prototype, Drag.prototype); // 繼承方法

BoundDrag.prototype.fnMove = function(e) {
    let left = e.clientX - this.disX;
    let top = e.clientY - this.disY;
    const maxLeft = document.documentElement.clientWidth - this.obj.offsetWidth;
    const maxTop = document.documentElement.clientHeight - this.obj.offsetHeight;
    
    left = Math.max(0, Math.min(left, maxLeft));
    top = Math.max(0, Math.min(top, maxTop));
    
    this.obj.style.left = left + 'px';
    this.obj.style.top = top + 'px';
};

// 使用
window.onload = function() {
    new Drag('div1').init();       // 自由拖拽
    new BoundDrag('div2').init();  // 邊界限制拖拽
};

配套 HTML/CSS

<style>
#div1, #div2 { width:100px; height:100px; position:absolute; }
#div1 { background:red; }
#div2 { background:blue; left:100px; }
</style>
<div id="div1"></div>
<div id="div2"></div>

六、原型繼承最佳實踐總結

  1. 組合繼承:實例屬性用 call,原型方法用原型鏈,最通用
  2. 寄生組合繼承:Sub.prototype = Object.create(Super.prototype) 避免兩次調用父構造函數 (最優)
  3. ES6 class:extends/super 只是語法糖,底層仍是原型機制
  4. 避免修改內置原型:污染全局,改用組合/裝飾模式
  5. 慎用 \_\_proto\_\_:非標準,現代用 Object.getPrototypeOf/Object.setPrototypeOf

原型鏈繼承圖 完整繼承鏈圖

JavaScript 面向對象的核心在於理解原型鏈委托機制。掌握各種繼承模式的優劣、原型屬性共享陷阱、constructor 修正、類型精準判斷,能寫出靈活復用、結構清晰的組件化代碼。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
方法用途示例
hasOwnProperty(prop)判斷自身屬性 (非繼承)arr.hasOwnProperty('push') // false
constructor獲取構造函數a1.constructor === Aaa // true
instanceof檢測原型鏈關係[] instanceof Array // true
Object.prototype.toString.call()精確類型判斷Object.prototype.toString.call([]) === '[object Array]'