構造函數
小編上篇博客中介紹到的通過關鍵字class方式定義類,然后根據類再創建對象的方式,是ES6中語法,現在很多瀏覽器對ES6的支持還不是很好,所以也要學習通過構造函數(構建函數)的方式創建對象
問?既然瀏覽器對ES6的支持不是很好,是不是編寫代碼時不要使用ES6語法呢?(看完這篇文章你就有答案了)
1.構造函數和原型
1.1對象的三種創建方式–復習
-
字面量方式
var obj = {};
-
new關鍵字
var obj = new Object();
-
構造函數方式
function Person(name,age){this.name = name;this.age = age; } var obj = new Person('zs',12);
說明:
構造函數是一種特殊的函數,主要用來初始化對象,即為對象成員的值進行初始化,其與new一起使用用于創建對象
這里所說的構造函數與前面講的類中的構造函數不太一樣,類中的構造函數主要是給類中的屬性賦值,這里的構造函數中,既可以包屬性,也可以包含方法,與上面所說的類的概念更接近,可以看成是J S 徹底向面向對象轉變過程中的一種過度,四不像
所以在編寫構造函數時,可以參考類的定義方式,將對象的一些公共的屬性和方法抽象出來,然后封裝到這個函數中
如下面分別使用ES6中的class和ES5中的構造函數的方式生成實例對象
/**ES6 中創建對象的方法:先使用class關鍵字創建類,然后使用 new 類名() 的方式創建類的對象*/class Person {constructor(name, age) {this.name = namethis.age = age}speak(){console.log('哇哇哇哇哇哇哇')}}// 創建類的對象var p1 = new Person('李白', 20)// 訪問類中的屬性console.log(p1.name)p1.speak()console.log('---------------------')/**ES5 中創建對象的方法:先創建構造函數,然后使用 new 函數名稱() 的方式創建這個函數的對象*/function Star(name, gender) {this.name = namethis.gender = genderthis.sing=function(){console.log('小呀嘛小二郎')}}// 通過構造函數創建對象var s1 = new Star('杜甫', '男')console.log(s1.gender)s1.sing()
圖解
注意兩點
1)構造函數首字母大寫,就給使用class創建類一樣
2)與new關鍵字一起使用,這更與class一樣
3)最為重要的一點是:類的編寫方法更像是語法糖,目的是讓我們能夠快速、舒服、優雅的編寫類,但是從本質上來說,類其實就是函數。初學者可能感覺不到 class 方式定義類相較于 構造函數的優勢:1)沒有體會到通過構造函數的方式實現繼承的痛苦,所以無法理解通過 ES6 中 extends 實現繼承的優勢; 2)沒有見識過傳統的面向變成語言
console.log(typeof Person);
console.log(typeof FPerson);
結果
new的解釋
在內存中創建一個新的空對象
讓this指向這個對象,所以在代碼中使用this,就是使用這個對象,this 跟類沒有關系
執行構造函數中的代碼,給這個對象添加屬性和方法,但是方法中的代碼不會執行
1.2靜態成員和實例成員
1.2.1實例成員
實例成員就是構造函數內部通過this添加的成員 如下列代碼中 name age sing 就是實例成員,實例成員只能通過實例化的對象來訪問
/*通過this添加的成員就是實例成員1)實例成員只能通過對象.成員的方式訪問2)實例成員與每個對象相關,也就是每個對象的成員的值是不一樣的*/function Star(name, gender) {this.name = namethis.gender = genderthis.sing = function () {console.log('小呀嘛小二郎')}}// 通過構造函數創建對象var s1 = new Star('杜甫', '男')var s2 = new Star('蔡徐坤', '女')console.log(s1.gender)console.log(s2.gender)// 實例成員不能通過 【構造函數名稱.成員】 的方式訪問// console.log(Star.gender) // undefined
這點與ES6中 class 創建的類是一樣的
class Star {constructor(uname, age) {this.uname = unamethis.age = age}sing() {console.log('我會唱歌')}
}var ldh=new Star('劉德華',17)
console.log(ldh.uname)
1.2.2靜態成員
靜態成員 在構造函數本身上添加的成員 如下列代碼中 就是靜態成員,靜態成員只能通過構造函數來訪問
這與通過 class 關鍵字定義類時一樣的
總結:
實例成員屬于對象,所以兩個對象的實例成員的值不一樣
靜態成員,屬于構造函數本身,每個對象都屬于這個構造函數,所以多個對象共享一個靜態成員
?
1.3構造函數的問題
構造函數方法很好用,但是存在浪費內存的問題。
1.4構造函數原型prototype
構造函數通過原型分配的函數是所有對象所共享的。
JavaScript 規定,每一個構造函數都有一個prototype 屬性,指向另一個對象。注意這個prototype就是一個對象,叫做原型對象
ES6 中的類也是一樣,因為我們說過,類從本質上來講,也是一個函數
function Star(name, gender) {this.name = namethis.gender = genderthis.sing = function () {console.log('小呀嘛小二郎')}}console.log(Star.prototype)
這個對象的所有屬性和方法,都會被構造函數所擁有
function Star(name, gender) {this.name = namethis.gender = genderthis.sing = function () {console.log('小呀嘛小二郎')}}// 為Star的原型對象添加方法(相當于將方法添加到Star的父類上去)Star.prototype.cry=function(){console.log('我要cry,cry,cry,cry,cry')}Star.prototype.dance=function(){console.log('一步一步,似魔鬼的步伐')}// 通過輸出發現,原型對象上確實有了 cry 方法console.log(Star.prototype)// 那么作為原型對象的子類的 Star 構造函數自然就擁有了cry 方法var s1 = new Star('杜甫', '男')s1.cry()s1.dance()var s2 = new Star('蔡徐坤', '女')s2.cry()s2.dance()
還可以使用對象的方式為原型添加多個方法
我們可以把那些不變的方法,直接定義在 prototype 對象上,這樣所有對象的實例就可以共享這些方法。
解惑:
1)這個原型,就類似于其他語言中的基類。。。。
2)不僅使我們自己使用構造函數或類定義的對象,JS中的內置對象的方法,其實都定義在這個對象的原型對象上
var arr=[]
// 查看對象的原型對象使用 __proto__ 屬性
console.log(arr.__proto__)
// 查看構造函數或類的原型對象使用 prototype 屬性
console.log(Array.prototype)
// 總結:對象的__proto__ 屬性和 類或者構造函數的 protptype 屬性指向的是同一個對象
1.5對象原型
構造函數的prototype 屬性獲取的是當前構造函數的原型對象
構造函數的實例的__proto__屬性獲取的是當前對象的對象原型
這兩者是一個對象,也就是說構造函數的原型對象與此構造函數的實例的對象原型是一個對象
function Star(name, gender) {this.name = namethis.gender = genderthis.sing = function () {console.log('小呀嘛小二郎')}}// 為Star的原型對象添加方法(相當于將方法添加到Star的父類上去)Star.prototype.cry = function () {console.log('我要cry,cry,cry,cry,cry')}Star.prototype.dance = function () {console.log('一步一步,似魔鬼的步伐')}// 通過輸出發現,原型對象上確實有了 cry 方法console.log(Star.prototype)// 那么作為原型對象的子類的 Star 構造函數自然就擁有了cry 方法var s1 = new Star('杜甫', '男')// s1.cry()// s1對象的 __proto__屬性獲取是的是s1對象的對象原型console.log(s1.__proto__)// 驗證構造函數的原型對象與實力的對象原型是一個對象console.log(Star.prototype===s1.__proto__)
1.6constructor構造函數
對象原型( proto)和構造函數(prototype)原型對象里面都有一個屬性 constructor 屬性(因為兩個其實是一個東西) ,constructor 我們稱為構造函數,因為它指回構造函數本身。
下面通過代碼理解
function Star(name, gender) {this.name = namethis.gender = genderthis.sing = function () {console.log('小呀嘛小二郎')}} var s1 = new Star('杜甫', '男')// 輸出構造函數的原型對象console.log(Star.prototype)// 輸出對象的對象原型console.log(s1.__proto__)
通過上面的代碼,我們看到,constructor 屬性的值確實是這個對象對應的構造函數
不僅可以通過這個屬性,獲取原型對象所屬的構造函數,constructor屬性 還可以讓原型對象重新指向原來的構造函數
一般情況下,對象的方法都在構造函數的原型對象中設置。
如下面這樣
// 為構造方法的原型對象中添加speak 方法
Person.prototype.speak=function(){console.log('人類說話')
}
但是如果加入多個方法,使用上面的方式就比較麻煩
function Star(name, gender) {this.name = namethis.gender = gender
}
// 像原型添加sing方法
Star.prototype.sing = function () {console.log('紅星閃閃放光彩');
}
// 向原型添加 dance 方法
Star.prototype.dance = function () {console.lo('魔鬼的步伐')
}
// 向原型添加fly
Star.prototype.fly = function () {console.log('上了飛機就拖鞋')
}
像上面這樣,如果要添加多個方法,我們可以給原型對象采取對象形式賦值,如下面的代碼
Star.prototype = {sing: function () {console.log('紅星閃閃放光彩');},dance: function () {console.lo('魔鬼的步伐')},fly: function () {console.log('上了飛機就拖鞋')}
}
但是這樣就會覆蓋構造函數原型對象原來的內容,這樣修改后的原型對象 constructor 就不再指向當前構造函數了
此時,我們可以在修改后的原型對象中,添加一個 constructor 指向原來的構造函數。
結果
總結:如果我們修改了原來的原型對象,給原型對象賦值的是一個對象,則必須手動的利用constructor指回原來的構造函數如:
完整代碼
function Star(name, gender) {this.name = namethis.gender = gender
}
Star.prototype = {construcotr:Star,sing: function () {console.log('紅星閃閃放光彩');},dance: function () {console.lo('魔鬼的步伐')},fly: function () {console.log('上了飛機就拖鞋')}
}
console.log(Star.prototype)
1.7原型鏈
? 每一個實例對象又有一個__proto__屬性,指向的構造函數的原型對象,構造函數的原型對象也是一個對象,也有__proto__屬性,這樣一層一層往上找就形成了原型鏈。
可以類比基類,基類就是Object
其實所有的自定義的或者系統內置的構造函數或者類的最頂級的對象原型都是 Object
1.8構造函數實例和原型對象三角關系
1.構造函數的prototype屬性指向了構造函數原型對象,構造函數的原型對象的constructor屬性指向了構造函數
function Star(name, age) {this.name = namethis.age = age
}
// 獲取輸出構造方法的原型對象
console.log(Star.prototype)
// 獲取并輸出原型對象的構造函數
console.log(Star.prototype.constructor)
// 證明原型對象的constructor屬性確實獲取的是對應的構造函數
console.log(Star.prototype.constructor===Star)
2.實例對象是由構造函數創建的,實例對象的__proto__屬性指向了構造函數的原型對象
// 實例對象由構造函數創建
var s1=new Star('肖戰',18)
// 實例對象的__proto__屬性指向了對應構造函數的原型對象
console.log(s1.__proto__)
3.構造函數的原型對象的constructor屬性指向了構造函數,實例對象的原型就是構造函數的原型對象,此對象中有constructor屬性也指向了構造函數
重要說明:上面所說的理論同樣適用于ES6中
class Star {constructor(name){this.name=name}
}
var s=new Star('yhb')
console.log(Star.prototype)
console.log(s.__proto__)
1.9原型鏈和成員的查找機制
任何對象都有原型對象,也就是prototype屬性,任何原型對象也是一個對象,該對象就有__proto__屬性,這樣一層一層往上找,就形成了一條鏈,我們稱此為原型鏈;
當訪問一個對象的屬性(包括方法)時,首先查找這個對象自身有沒有該屬性。
如果沒有就查找它的原型(也就是 __proto__指向的 prototype 原型對象)。
如果還沒有就查找原型對象的原型(Object的原型對象)。
依此類推一直找到 Object 為止(null)。
__proto__對象原型的意義就在于為對象成員查找機制提供一個方向,或者說一條路線。
1.10原型對象中this指向
構造函數中的this和原型對象的this,都指向我們new出來的實例對象
1、構造函數中的this指向的就是new出來的對象
function Star(name, age) {this.name = namethis.age = ageconsole.log(this)
}
var s1=new Star('李白',20)
var s2=new Star('杜甫',17)
2、構造函數的原型對象上的this指向的也是new出來的對象
function Star(name, age) {this.name = namethis.age = ageconsole.log(this)
}
Star.prototype.sing=function(){console.log(this)
}
var s1=new Star('李白',20)
var s1=new Star('杜甫',17)
通過下面的代碼也可以比較兩個到底是不是都只想了 new 出來對象
function Star(name, age) {this.name = namethis.age = age }var that = null// 構造函數的原型對象中的thisStar.prototype.sing = function () {that = this}var s1 = new Star('李白', 20)// 韓炳旭說:sing方法一定要調用,否則無法執行賦值操作s1.sing()console.log(that === s1)
1.11通過原型為數組擴展內置方法
查看數組的原型
console.log(Array.prototype);
為數組擴展和一個方法,可以計算數組中元素的和
var numbers = new Array(1, 2, 3, 4, 5)// 傳統方法求和// var sum = 0// for (var i = 0; i < numbers.length; i++) {// sum += numbers[i]// }// 通過改變原型Array.prototype.sum = function () {var sum = 0for (var i = 0; i < this.length; i++) {sum += this[i]}return sum}console.log(numbers.sum())// 再創建一個數組,使用[]方式創建的也是Array類的實例var arr=[2,3,4,7]console.log(arr.sum())console.log(Array.prototype)
2.繼承
在ES6之前,沒有extends 關鍵字實現類的繼承,需要通過構造函數+原型對象的方式模擬實現繼承,這種方式叫做組合繼承
2.1call()
- call()可以調用函數
- call()可以修改this的指向,使用call()的時候 參數一是修改后的this指向,參數2,參數3(普通參數)…使用逗號隔開連接
下面代碼單純的調用函數
function f1() {console.log(this) //window
}
f1.call()
下面代碼修改this的指向
function f1() {/**1)默認情況下,this=window*2)使用call修改后,this=p*/console.log(this) }f1.call() // 輸出window// 創建對象pvar p={}// 將函數f1的this指向修改為對象pf1.call(p) // 輸出p
上面call 方法的第一個參數就是this引用的新對象,后面的參數作為函數 f1 的普通參數
代碼演示
2.2子構造函數繼承父構造函數中的屬性
下面利用上面講到的知識實現構造函數的繼承(所謂繼承,就是讓一個類擁有另一個類中的屬性和方法,換言之,就是將A類中的屬性和方法的宿主換成B的對象)
步驟如下
- 先定義一個父構造函數
- 再定義一個子構造函數
- 子構造函數繼承父構造函數的屬性(使用call方法)
通過調試證明上面的結論
上面的案例只演示了屬性的繼承,方法的繼承是一樣的
2.3借用原型對象繼承方法
上面的方式,可以繼承構造函數(為了簡單,以后我們稱其為類)中的方法,但前面講過,實際開發中,我們更喜歡將方法注冊到類的原型上
function Father(name, age) {this.name = namethis.age = age}Father.prototype.speak = function () {console.log('hello')}function Son(gender) {// 注釋 ①Father.call(this, '張三', 20)this.gender = gender}var f=new Father('yhb',30)console.log(f) var s = new Son('女')console.log(s)
輸出結果
所以,Son 的對象中就沒有 speak 方法
其實原因很簡單,在Father的原型對象上添加了speak方法,那么Father會繼承這個方法,但當前 Son與Father以及Father的原型并沒有任何關系,我們只不過通過 call 方法巧妙的使用了其 name和age 屬性而已,所以Son中自然沒有speak 方法
如何解決呢?
我們可以修改Son的原型為Father的原型,這樣的話Son就擁有了speak 方法
輸出結果
我們發現,Son中確實擁有了speak 方法
但是問題也隨之出現,因為 Father 與 Son 此時的原型對象是一個,所以此時我們如果想 Son的原型對象上添加一個方法
Son.prototype.run=function(){console.log('run')}
再分別輸出兩個對象
這就違反了繼承的原則:只能兒子繼承父親的,但現在是父親也能繼承兒子的
解決方案:將下面的代碼
Son.prototype=Father.prototype
替換成下面這個
Son.prototype=new Father()
也就是讓Son的原型對象指向 Father 的對象實例,這個實例的 proto 屬性指向Father的對象原型,所以其中具有speak方法,但這僅僅是一個實例對象,就想從Father類拷貝了一份,修改此對象的原型,不會影響ather類的對象,所以修改此實例,不會影響到Father 的原型對象
稍微有點小問題就是,此時 Son的構造函數指向的是Father的構造函數了
console.log(s.constructor)
使用下面的代碼再將其構造函數指回Son的構造函數
Son.prototype.constructor=Son
完整代碼
/* 利用call 實現構造函數的繼承*/function Father(name, age) {this.name = namethis.age = age}Father.prototype.speak = function () {console.log('hello')}function Son(gender) {// 注釋 ①Father.call(this, '張三', 20)this.gender = gender}Son.prototype=new Father()// 修改Son的構造函數為SonSon.prototype.constructor=Son// 像Son的原型對象中添加一個方法Son.prototype.run=function(){console.log('run')}//var f=new Father('yhb',30)// console.log(f) var s = new Son('女')console.log(s)console.log(s.constructor)
圖解:
3.ES5新增方法
關于 ECMAScript 與 JS 的關系,以及 ES 各個版本,大家可以自行百度,小編在這里不做展開說明
ES6 是在ES5的基礎上升級,所以先學習ES5新增的特性,后面再學習ES6新增的特性
3.1數組方法forEach遍歷數組
此方法用來便利數組,參數為一個函數
以后再想便利數組就不用自己編寫循環了
var numbers = [1, 3, 5, 7, 9]
numbers.forEach(function (item, index, ary) {console.log('元素:' + item);console.log('索引:' + index);console.log('數組本身:' + ary);
})
案例:數組求和
var sum=0
numbers.forEach(function(item){sum+=item
})
console.log(sum)
3.2數組方法filter過濾數組
此方法有一個返回值,用于接收所有符合過濾條件的元素,返回值的類型為數組
var numbers = [1, 2, 3, 4, 5, 6]
/*下面代碼從數組numbers中篩選出所有>4的元素,然后放到新數組result中注意:一定要有return 關鍵字*/
var result = numbers.filter(function (item, index) {return item > 4
})
console.log(result);
輸出結果
3.3數組方法some
some 查找數組中是否有滿足條件的元素 var arr = [10, 30, 4];var flag = arr.some(function(item,index,array) {//參數一是:數組元素//參數二是:數組元素的索引//參數三是:當前的數組return item < 3;});
console.log(flag);//false返回值是布爾值,只要查找到滿足條件的一個元素就立馬終止循環
**some()**
方法測試數組中是不是至少有1個元素通過了被提供的函數測試。它返回的是一個Boolean類型的值。
如果用一個空數組進行測試,在任何情況下它返回的都是false
。
語法:
arr.some(callback(element[, index[, array]])[, thisArg])
1)返回值為布爾值
2)找到滿足條件的元素,就終止循環,不會繼續向后查找
可以利用這個函數查詢數組中是否存在某個值
var numbers = [1, 2, 3, 4, 5, 6]function checkAvailability(arr, val) {var res = arr.some(function (element, index) {return element == val})return res}console.log(checkAvailability(numbers, 1))
3.4找水果案例
首先將數據定義在數組中,然后便利數組中的數據渲染到表格中,然后可以根據價格或者名稱查詢想吃的水果
-
定義數組對象數據
var fruits = [{id: 001,title: '蘋果',price: 5},{id: 001,title: '蘋果',price: 5},{id: 002,title: '梨',price: 3},{id: 003,title: '香蕉',price: 6},{id: 004,title: '砂糖橘',price: 2} ]
-
使用forEach遍歷數據并渲染到頁面中
var tbody = document.querySelector('tbody')fruits.forEach(function (item) {var tr = ` <tr><td>${item.id}</td><td>${item.title}</td><td>${item.price}</td></tr>`tbody.insertAdjacentHTML('beforeend',tr)})
-
根據價格篩選數據
將篩選出的數據重新渲染到表中
function setData(ary) {// 先刪除以前的數據tbody.innerHTML = ''ary.forEach(function (item) {var tr = ` <tr><td>${item.id}</td><td>${item.title}</td><td>${item.price}</td></tr>`tbody.insertAdjacentHTML('beforeend', tr)})}// 根據價格過濾商品var btnPrice = document.querySelector('#btnPrice')var min_price = document.querySelector('#min_price')var max_price = document.querySelector('#max_price')btnPrice.addEventListener('click', function () {var result = fruits.filter(function (item) {return item.price >= min_price.value && item.price <= max_price.value})// 重新渲染數據setData(result)})
- 根據名稱搜索數據
// 根據名稱搜索var btnTitle = document.querySelector('#btnTitle')var title = document.querySelector('#title')btnTitle.addEventListener('click', function () {var result = fruits.filter(function (item) {return item.title == title.value
})// 重新渲染表格setData(result)})
上面數組中,水果名稱是唯一的
使用filter會便利每一個對象,然后比較,查詢效率相對較低
find 方法只要查詢到一個符合條件的就會停止,所以效率相對較高
btnTitle.addEventListener('click', function () {// find 方法返回的不是數組,而是對象,所以要使用foreach,需要將// 返回的對象放到數組中var result = fruits.find(function (item) {console.log(item)return item.title == title.value})var arr = []arr.push(result)// 重新渲染表格setData(arr)})
完整代碼
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title><style>* {margin: 0;padding: 0;}.box {width: 1000px;margin: 50px auto;}table,th,td {border: 1px solid #ccc;}table {width: 800px;border-collapse: collapse;text-align: center;margin-top: 20px;}th,td {padding: 5px 10px;}</style>
</head><body><div class="box"><label for="">價格</label><input type="text" id="min_price"><span>-</span><input type="text" id="max_price"><button id="btnPrice">搜索</button><label for="">名稱</label><input type="text" id="title"><button id="btnTitle">搜索</button><table><thead><tr><th>編號</th><th>名稱</th><th>價格</th></tr></thead><tbody></tbody></table></div><script>var fruits = [{id: 001,title: '蘋果',price: 5},{id: 002,title: '桃子',price: 10},{id: 003,title: '梨',price: 3},{id: 004,title: '香蕉',price: 6},{id: 005,title: '砂糖橘',price: 2}]// 獲取tbodyvar tbody = document.querySelector('tbody')setData(fruits)// 將數組中的數據渲染到表格中function setData(ary) {// 先刪除以前的數據tbody.innerHTML = ''ary.forEach(function (item) {var tr = ` <tr><td>${item.id}</td><td>${item.title}</td><td>${item.price}</td></tr>`tbody.insertAdjacentHTML('beforeend', tr)})}// 根據價格過濾商品var btnPrice = document.querySelector('#btnPrice')var min_price = document.querySelector('#min_price')var max_price = document.querySelector('#max_price')btnPrice.addEventListener('click', function () {var result = fruits.filter(function (item) {return item.price >= min_price.value && item.price <= max_price.value})// 重新渲染數據setData(result)})// 根據名稱搜索var btnTitle = document.querySelector('#btnTitle')var title = document.querySelector('#title')btnTitle.addEventListener('click', function () {// find 方法返回的不是數組,而是對象,所以要使用foreach,需要將// 返回的對象放到數組中var result = fruits.find(function (item) {console.log(item)return item.title == title.value})var arr = []arr.push(result)// 重新渲染表格setData(arr)})</script>
</body></html>
3.5some和forEach區別
- 如果查詢數組中唯一的元素, 用some方法更合適,在some 里面 遇到 return true 就是終止遍歷 迭代效率更高
- 在forEach和filter 里面 return 不會終止迭代
3.6 trim方法去除字符串兩端的空格
var str = ' hello '
console.log(str.trim()) //hello 去除兩端空格
var str1 = ' he l l o '
console.log(str.trim()) //he l l o 去除兩端空格
3.7獲取對象的屬性名
Object.keys(對象) 獲取到當前對象中的屬性名 ,返回值是一個數組
var obj = {id: 1,pname: '小米',price: 1999,num: 2000
};
var result = Object.keys(obj)
console.log(result)//[id,pname,price,num]
3.8Object.defineProperty
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
Object.defineProperty設置或修改對象中的屬性
Object.defineProperty(對象,修改或新增的屬性名,{value:修改或新增的屬性的值,writable:true/false,//如果值為false 不允許修改這個屬性值enumerable: false,//enumerable 如果值為false 則不允許遍歷configurable: false //configurable 如果為false 則不允許刪除這個屬性 屬性是否可以被刪除或是否可以再次修改特性
})
利用上面的方法,我們可以在不為文本框添加事件的同時,將文本框的值與對象中的某個屬性關聯起來
var username=document.querySelector('#username')
var data = {}
Object.defineProperty(data, "title", {get: function () {return username.value},set: function (newValue) {username.value=newValue},enumerable: true,configurable: true
})