一.數據類型
在javascript當中數據類型總共分爲兩類:基本類型和引用類型;基本類型是有6種分別是:null,undefined,boolean,number,string和symbol(es6新增,表示獨一無二的值,具體可以看阮一峯的介紹);引用類型統稱爲Object對象,主要包括對象,數組.
基本類型和引用類型的區別:
主要是存儲位置不一樣,基本類型直接存儲在棧當中,而引用類型只是把指針存儲在棧中,對應的值是存儲在堆中的.
數據類型檢測方法:
方法1:typeof
除了null,其它都可以檢查;
方法2:A(對象) instanceof B(構造函數)
判斷這個構造函數的prototype屬性, 在不在這個對象的原型鏈上.。如果在則返回true,否則返回false。比如:console.log([] instanceof Array);//true(檢測方法不準確,因爲[] instanceof Object)也是true;
方法3:Object.prototype.toString.call(傳入要檢測的數據)
這個方法是萬能的檢測數據類型,比如:console.log(Object.prototype.toString.call(null)); //’[object Null]’
方法4:Array.isArray([])
這是數組單獨的檢測方法,比如:console.log(Array.isArray([]))//true
二.預解析
預解析的規律:
1.把變量的聲明提升到當前作用域的最前面,只會提升變量的聲明,不會提升賦值;
2.把函數的聲明提升到當前作用域的最前面,只會提升函數的聲明,不會提升調用;
3.在同一級作用域裏存在變量聲明和函數聲明,優先會提升變量聲明;
演示代碼1:
var a = 25;
function abc (){
alert(a);//undefined
var a = 10;
}
abc();
console.log(a);//25
function a() {
console.log('aaaaa');//沒有調用,不會執行
}
var a = 1;
console.log(a);//1
預解析會變爲:
var a;
function abc (){
var a;
alert(a);//undefined
a = 10;
}
function a() {
console.log('aaaaa');//沒有調用,不會打印
}
a = 25;
abc();
console.log(a);//25
a = 1;
console.log(a);//1
演示代碼2:(涉及多個賦值的拆解)
f1();
console.log(c);
console.log(b);
console.log(a);
function f1() {
var a = b = c = 9;
console.log(a);
console.log(b);
console.log(c);
}
預解析後變爲:
function f1() {
//注意多個賦值拆解規律:變量聲明第一,最後賦值第二,依次後往前的規律賦值
var a;
c=9;
b=c;
a=b;
console.log(a);//9
console.log(b);//9
console.log(c);//9
}
f1();
console.log(c);//9
console.log(b);//9
console.log(a);//只有局部f1裏纔有,全局裏沒有,直接報錯
三.對象
創建對象的方法
引言:我平時項目中一般四種方法比較常用,但我看了<<javascript高級程序設計>>這本書之後發現有9種方法,分別是:
1.字面量方法:
var person = {
name: "小明",
age: 18,
sayHi: function () {
console.log("hi");
}
}
console.log(person);
2.new Object()創建方法:
var person =new Object();
person.name="小明";
person.age=18;
person.sayHi=function(){
console.log("hi");
}
console.log(person);
3.工廠模式創建對象方法:(是new Object()創建的升級,實際是封裝函數後將對象返回出來)
function createPerson(name,age,logData){
var person =new Object();
person.name=name;
person.age=age;
person.sayHi=function(){
console.log(logData);
}
return person;
}
var person=createPerson('小明',18,"hi")
console.log(person);
4.構造函數方法:
function Person(name,age,logData){
this.name=name;
this.age=age;
this.sayHi=function(){
console.log(logData);
}
console.log(this);//this是對象本身
}
var person=new Person('小明',18,"hi")
console.log(person);
構造函數寫法兩個注意點:
a.函數首字母要大寫;
b.要使用new關鍵字;
構造函數new關鍵字做的四件事:
提示:結合工廠函數和構造函數對比發現
a.new會在內存中用new Object()方法創建一個新的空對象;
b.new會讓this指向這個心的空對象;
c.執行構造函數,給這個新的空對象添加屬性和方法;
d.最後,new會將有值的新對象進行返回;
注意:
因爲new關鍵字會自動幫我們返回他創建的對象,所以一般在構造函數中是不用寫return返回值的, 那萬一構造函數中就是寫了return返回值,這樣看返回值是簡單類型還是引用類型,簡單類型不影響,如果是複雜類型,那麼這個返回值會覆蓋原來new關鍵字自動返回的那個對象,這樣使創建的對象直接變爲複雜類型值.
5.原型鏈方法:也是使用了構造函數,但構造函數參數和函數體都是空,我們直接在函數點prototype點加要添加的屬性名=值;
function Person() {}
Person.prototype.name = "小明";
Person.prototype.age = 18;
Person.prototype.sayHi = function () {
console.log("hi");
}
var person = new Person();
console.log(person);//它會從原型裏找到
6.構造函數和原型鏈組合方法:(實際就是一部分屬性寫在構造函數裏,一部分屬性寫在原型裏)
function Person(name) {
this.name = name;
}
Person.prototype = {
constructor: Person, //這裏不寫也不會受影響
age: 18,
sayHi: function () {
console.log("hi");
}
}
var person = new Person("小明")
console.log(person);
7.構造函數動態原型方法:(實際構造函數裏面加了一個方法判斷,判斷方法不存在的情況下才加方法)
function Person(name,age,fnData){
this.name=name;
this.age=age;
if(typeof this.sayHi !='function'){
Person.prototype.sayHi=function(){
console.log(fnData);
}
}
}
var person=new Person('小明',18,"hi");
console.log(person);
8.寄生構造函數方法:(書中說了跟工廠模式一模一樣)
9.穩妥的構造函數方法:(實際就是沒有使用new關鍵字調用,在函數裏面用new Object創建對象,在函數裏面將對象進行返回)
function Person(name,age,fnData) {
var person =new Object()
person.name=name;
person.age=age;
person.sayHi=function(){
console.log(fnData);
}
return person
}
var person=Person("小明",18,"hi")
console.log(person.sayHi());
四.函數內部this指向
首先我們要知道這幾個點:
1.javascript是一種弱類型(可以隨意賦值類型),動態類型檢查(運行時才檢查)的語言,這就導致函數內部this指向聲明的話是不確定的,只有在調用時纔可以確定;
2.所以我們可以總結一句很有用的話:不管函數或者方法是如何聲明的,要看這個函數或者方法是最終誰最終點的,這個this就指向誰.
案例:
//1.普通函數中的this:指向window
function welcome(){
console.log(this);
}
welcome();//window,相當於window.welcome();
// ----------------------------------
// 2.對象方法中的this:對象點出來,指向這個對象
var obj={
name:"小明",
sayHi:function(){
console.log(this);
}
}
obj.sayHi();//指向obj對象
// ----------------------------------
// 對象裏的對象,最終誰點出來就是誰
var p1={
dog:{
name:'阿黃',
sayHi:function(){
console.log(this);
}
}
}
console.log(p1.dog.sayHi());//this是dog對象
// ----------------------------------
// 3.構造函數裏的this,指向構造函數實例化的對象,這個對象值是已經添加對應的屬性和值的對象
function Student(name,age) {
this.name=name;
console.log(this);
this.age=age;
}
var s=new Student('小明',18)//打印有小明的對象
// ---------------------------
// 構造函數忘記使用new的話,指向window
function Student(name,age) {
this.name=name;
console.log(this);//沒有寫new關鍵字,相當於給window添加屬性和方法
this.age=age;
}
var s= Student('小明',18)//打印window
五.修改函數內部this執行的方法(又叫函數的上下文調用模式)
我們可以使用Function.prototype裏的call(),apply()和bind()方法,所以只要是函數,都可以使用這個方法.
//1.call();
// 語法:函數名.call(修改後的this指向,參數1,參數2,...)
// 特點:會調用這個函數
function getSum(a,b) {
console.log(a+b);
console.log(this);
}
// getSum(1,2)//普通調用,this指向window
getSum.call(obj,1,2)//this指向obj對象了
// 2.apply();
// 語法:函數名.apply(修改後的this指向,[參數1,參數2,...]),與call()不一樣的是apply方法只能寫兩個參數,參數2是數組,裏面可以寫多個要傳遞的元素
// 特點:會調用這個函數
//兩個總結:apply方法只能寫兩個參數,第二個參數是數組,數組裏面可以寫多個參數;而call方法裏可以直接寫要傳遞的多個參數.
function getSum(a,b) {
console.log(a+b);
console.log(this);
}
// getSum(1,2)//普通調用,this指向window
getSum.apply(obj,[1,2])//this改爲了obj對象
// 3.bind();
// 語法:函數名.bind(新的this指向對象,參數1,參數2...)
// 特點:最大特點不會調用,只是返回一個修改了this後並攜帶參數的函數
function getSum(a,b) {
console.log(a+b);
console.log(this);
}
// getSum.bind(obj)//不會打印,代表不會調用
var fn=getSum.bind(obj,1,1);
// console.log(fn);//這裏還是添加了默認參數的getSum函數
fn()//這裏調用,打印this是obj對象
修改上下文注意點:
call和apply性能對比哪個好一些?
三個以下的參數傳遞時兩者性能差不多,三個以上的參數call方法會比apply好一些,所以工作中最好用call,當要傳遞的參數剛好是數組時可以直接使用apply,當然也可以用擴展運算符改成call方法也是可以的.
性能測試辦法(供參考)
// 性能測試辦法
console.time('A');
for(let i=0;i<1000000;i++){
}
console.timeEnd('A');
六.原型鏈
答:javascript是面向對象的,每一個實例化的對象都有一個proto屬性,這個屬性指向它的原型對象,同時,這個實例化對象的構造函數有一個屬性prototype,與對象的proto屬性指向同一個原型,當一個對象在查找一個屬性或方法時,它會第一時間看自己有沒有,如果自己沒有它會根據proto向它的原型進行查找,如果也沒有,它會繼續像原型的原型裏繼續查找,直到查到Object.prototype._proto爲null,即原型鏈的終點null,這樣就形成了一個完整的原型鏈.
我對原型圖總結了自己的三條經驗,記住這三條就很好理解
1.只要是構造函數,它就有prototype屬性,同時構造函數也是對象,它就有proto屬性;
2.一個實例化的對象對應的proto完整的路線有四條:實例化對象.proto第一條;構造函數.proto第二條;構造函數Function.proto第三條;構造函數Object.proto第四條;
3.Function.proto和Function.prototype指向同一個Function.prototype;
完整的原型鏈圖:
補充:記住規律對象proto的起始主線有四條:p1.proto;Person.proto;Function.proto,Object.proto;
內置對象(比如下面的Array,Date)的原型鏈:
七.繼承的方式
1.混入式繼承:實際就是遍歷父元素的對象,在對象的循環體裏將父元素的值賦值給子類即可(sonObj[key]=fatherObj[key]).
// 混入式
var wangjianlin = {
house:{
price:10000000,
adress:'洛杉磯'
},
car:{
price:5000000,
brand:'勞斯萊斯幻影'
}
};
var wangsicong = {
grilFriends:['豆得兒','雪梨','張馨予','林更新','001號']
};
//wangsicong這個對象想擁有wangjianlin這個對象的車和房.繼承
for(var key in wangjianlin){
wangsicong[key] = wangjianlin[key];
}
console.log(wangsicong);//這樣就有了父類和自己的屬性和方法
2.替換原型式繼承:實際就是將父類的值賦值給子類構造函數的原型(即Son.prototype=fatherObj).
//替換原型的方式.
var wangjianlin = {
house:{
price:10000000,
adress:'洛杉磯'
},
car:{
price:5000000,
brand:'勞斯萊斯幻影'
}
};
//渣男構造構造
function ZhaNan(gfs){
this.gfs = gfs;
}
//每一個渣男都有一個騙女孩子的方法.
ZhaNan.prototype.huaQian = function () {
console.log("花錢請喫6塊錢麻辣燙...");
}
//替換原型繼承.(重點操作)
ZhaNan.prototype = wangjianlin;
//實例化一個渣男對象.
var wangsicong = new ZhaNan(['豆得兒','雪梨','張馨予','林更新','001號']);//給自己添加了gfs屬性,值是這個數組
console.log(wangsicong);
console.log(wangsicong.car);//可以得到得到父對象裏的car屬性值
3.混合式繼承:
實際也是遍歷父類對象,在循環體裏用父類對象的值賦值給子類構造函數的原型裏(即Son.prototype[key]=fatherObj[key]).
var wangjianlin = {
house:{
price:10000000,
adress:'洛杉磯'
},
car:{
price:5000000,
brand:'勞斯萊斯幻影'
}
};
//渣男構造構造
function ZhaNan(gfs){
this.gfs = gfs;
}
//每一個渣男都有一個騙女孩子的方法.
ZhaNan.prototype.huaQian = function () {
console.log("花錢請喫6塊錢麻辣燙...");
}
//混合式繼承.
//這裏並沒有替換原型,而是給原型增加了屬性和方法.
for(var key in wangjianlin){
ZhaNan.prototype[key] = wangjianlin[key];
}
//實例化一個渣男對象.
var wangsicong = new ZhaNan(['豆得兒','雪梨','張馨予','林更新','001號']);
console.log(wangsicong);
console.log(wangsicong.car);//也可以得到得到父對象裏的car屬性值
4.class類的繼承:
class類的繼承的核心在於使用extends表明繼承哪個類,並且在子類的構造函數中必須調用super()方法;
// 父類
class Parent {
constructor(props) {
this.name = props;
this.house = {
price: 10000000,
adress: '洛杉磯'
};
this.car = {
price: 5000000,
brand: '勞斯萊斯幻影'
}
}
getValue() {
console.log("我是父類的方法");
}
}
// 子類
class Child extends Parent {
constructor(props) {
console.log(props);//就是傳遞過來的參數
super(props);
this.name = props;
}
}
let child = new Child("小明")
console.log(child.getValue()); //可以調用父類方法
console.log(child.car); //得到父親的car
八.arguments關鍵字
每一個函數都有一個單獨的arguments對象,它只能寫在函數裏面,它用來獲取函數傳遞過來的所有的實參,如果在函數裏面修改了參數,arguments也會變,它是僞數組,沒有數組的方法,但有數組的長度屬性,所以,作用:可以根據參數的數量來執行不同的業務邏輯.
九.閉包
定義:父函數A內部有一個子函數B,子函數B調用的時候可以訪問到父函數A中的變量,而在全局裏的其它人訪問不到,那麼子函數B就是閉包.
function A() {
var a=1;
window.B=function(){
console.log(a);
}
}
A();//先執行一下,因爲裏面要創建裏面內容,創建完後一直存在,不會回收
B();//得到1
閉包的意義就是讓我們可以間接的訪問函數內部的變量;
經典面試題:
// 經典題目:下面會打印什麼?
for(var i=0;i<=5;i++){
setTimeout( ()=> {
console.log(i);
}, 100);
}
//這裏會打印六個6,改爲0,1,2...5的方法:
// 方法1,改成自執行函數,並將i傳遞過去,裏面用function接收,其餘函數體就照常(利用了閉包)
for(var i=0;i<=5;i++){
//這裏可以訪問到i
(function(j){//自執行函數裏接收傳遞過來的i
setTimeout( ()=> {
console.log(j);
}, 100)
})(i)
}
// 方法2:定時器添加第三個參數並接收即可
for(var i=0;i<=5;i++){
setTimeout( (j)=> {
console.log(j);
}, 100,i);
}
// 方法3:直接用let
for(let i=0;i<=5;i++){
setTimeout( ()=> {
console.log(i);
}, 100);
}
閉包有兩個作用:
1.延長函數裏面變量的生命週期;
2.提供有限的訪問權限;
十.遞歸
就是函數內部調用自己,同時要有結束條件.
遞歸應用:
//1.用遞歸求1-n之間的整數的累加和. n=5
//1+2+3+4+5 getSum(n)
//1+2+3+4 +5
//1+2+3 +4
//1+2 +3
//1 +2
//如果我們寫了一個函數getSum就是用來求1-n之間的整數累加和的.
//那在求1-n之間累加和的時候, 要先求1- (n-1)之間的累加和,再加上n本身.
function getSum(n){
if(n == 1){
return 1;
}
return getSum(n-1) + n;
}
console.log(getSum(5));
//2.求斐波那契數列中第n位的數是多少.
//1 1 2 3 5 8 13 21 34.....
//如果我們寫了一個函數getFB()就是用來求斐波那契數列中第n是多少的.
//要求n位是多少,要先求n-1位和n-2位是多少.
function getFB(n){
if(n==1 || n==2){
return 1;
}
return arguments.callee(n-1) + getFB(n-2)//arguments.callee就是getFB
}
console.log(getFB(10));
//3.遞歸求頁面上所有的元素.
var list = [];
function getHDeles(ele){
var children = ele.children;//元素集合,是arr,這是求出這個ele元素的所有子代.
for(var i = 0 ; i < children.length; i++){
var child = children[i]; //children[i]//得到每一個元素
list.push(child);//把元素的子代存進來.
//子代也要求子代.調用這個函數求子代
getHDeles(child);
}
}
//驗證一下.
//求id爲father下的所有元素
var father = document.getElementById("father");
getHDeles(father);
console.log(list);
//求頁面上所有的元素.求dom樹.
getHDeles(document);
// console.log(list);
十一.防抖和節流:
防抖概念:任務頻繁觸發的情況下,只有任務觸發的間隔超過指定間隔的時候,任務纔會執行。(意思就是這件事兒需要等待,如果你反覆催促,我就重新計時!核心是定時器)
應用場景有:搜索聯想、input驗證、resize
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>防抖</title>
</head>
<body>
<button id="debounce">點我防抖!</button>
<script>
window.onload = function() {
// 1、獲取這個按鈕,並綁定事件
var myDebounce = document.getElementById("debounce");
myDebounce.addEventListener("click", debounce(sayDebounce,2000));
}
// 2、防抖功能函數,接受傳參
function debounce(fn,delay) {
// 4、創建一個標記用來存放定時器的返回值
let timeout = null;
return function() {//返回一個函數
// 5、每次當用戶點擊/輸入的時候,把前一個定時器清除(這裏是重點)
clearTimeout(timeout);
// 6、然後創建一個新的 setTimeout,
// 這樣就能保證點擊按鈕後的間隔內如果用戶還點擊了的話,就不會執行 fn 函數
timeout = setTimeout(() => {
fn.call(this, arguments);//這句話是調用fn,並把不確定的實參傳過去
}, delay);//這裏設置限定時間
};
}
// 3、需要進行防抖的事件處理
function sayDebounce() {
// ... 有些需要防抖的工作,在這裏執行
console.log("防抖成功!");
}
</script>
</body>
</html>
效果:
防抖案例2:切換頁面讀取內存案例
節流概念:指定時間間隔內只會執行一次任務。核心概念是時差,時差還在我就直接return這個函數.
應用場景有:scroll、touchmove
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>節流</title>
</head>
<body>
<button id="throttle">點我節流!</button>
<script>
window.onload = function() {
// 1、獲取按鈕,綁定點擊事件
var myThrottle = document.getElementById("throttle");
myThrottle.addEventListener("click", throttle(sayThrottle,3000));
}
// 2、節流函數體
function throttle(fn,delay) {
// 4、通過閉包保存一個標記
let canRun = true;
return function() {
// 5、在函數開頭判斷標誌是否爲 true,不爲 true 則中斷函數(這裏是重點)
if(!canRun) {
return;
}
// 6、將 canRun 設置爲 false,防止執行之前再被執行
canRun = false;
// 7、定時器
setTimeout( () => {
fn.call(this, arguments);
// 8、執行完事件(比如調用完接口)之後,重新將這個標誌設置爲 true
canRun = true;
}, delay);
};
}
// 3、需要節流的事件
function sayThrottle() {
console.log("節流成功!");
}
</script>
</body>
</html>
效果:
十二.重繪與迴流(也叫重排)
重繪(repaint):當元素樣式的改變不影響佈局時,瀏覽器將使用重繪對元素進行更新,此時由於只需要 UI 層面的重新像素繪製,因此損耗較少。
重繪應用場景:改變元素顏色,改變元素背景色…
迴流(reflow):又叫重排(layout)。當元素的尺寸、結構或者觸發某些屬性時,瀏覽器會重新渲染頁面,稱爲迴流。此時,瀏覽器需要重新經過計算,計算後還需要重新頁面佈局,因此是較重的操作。
迴流應用場景:瀏覽器窗口大小改變,元素尺寸/位置/內容發生改變,元素字體大小變化,添加或者刪除可見的 DOM 元素…
總結:迴流必定會觸發重繪,重繪不一定會觸發迴流。重繪的開銷較小,迴流的代價較高。
那麼,在工作中我們要如何避免大量使用重繪與迴流呢?:
1.避免頻繁操作樣式,可彙總後統一一次修改
2.儘量使用 class 進行樣式修改,而不是直接操作樣式
3.減少 DOM 的操作,可使用字符串一次性插入
十三.淺深拷貝
我們知道對象是引用類型,假設有一個對象A和對象B,將對象A的值賦值給對象B,當你改了任意一個對象的值時,另一個對象的值都會一起變,而我們實際開發過程中也經常會遇到:後端小夥伴需要我將 接口返回的源數據和頁面修改後新數據各發一份給它.這就需要用到對象的拷貝了.
1.淺拷貝:只拷貝對象的第一層就叫淺拷貝.
淺拷貝方法:自己寫函數,assign,concat,slice,擴展運算符方法這五種
a.自己寫淺拷貝
const obj1 = {
name: "小明",
hobby: ["喫飯", "睡覺"]
};
function shallowClone(obj) {
const obj2 = {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
obj2[key] = obj1[key];
}
}
return obj2;
}
const obj2=shallowClone(obj1);
obj2.name="小張"
console.log(obj2);//得到一個新的對象
console.log(obj1);
js
b.利用Object.assign()方法
const obj1 = {
username: 'LiangJunrong',
skill: {
play: ['basketball', 'computer game'],
read: 'book',
},
girlfriend: ['1 號備胎', '2 號備胎', '3 號備胎'],
};
const obj2 = Object.assign({}, obj1);
console.log(obj2)
c.利用數組的concat()鏈接方法
concat方法不會改變原有數組,而是返回一個新數組.
const arr1 = [
1,
{
username: 'jsliang',
},
];
let arr2 = arr1.concat();
console.log(arr2)
d.使用數組的slice()截取方法
slice() 方法原數組不會改變,而是返回一個新的數組對象,這一對象是一個由 begin 和 end 決定的原數組的淺拷貝…
const animals = ['ant', 'bison', 'camel', 'duck', 'elephant'];
console.log(animals.slice(2, 4));
// expected output: Array ["camel", "duck"]
e.利用…展開運算符
const obj1 = {
name: "小明",
hobby: ["喫飯", "睡覺"]
};
const obj2 = { ...obj1}
console.log(obj2); //得到一個新的對象
console.log(obj1);
上面都只能拷貝一層,如果要拷貝對象兩層或者以上,就要用深拷貝了
深拷貝的方法:
a.使用JSON.parse(JSON.stringyfy(oldObj))方法
const arr1 = [
1,
{
username: 'jsliang',
},
];
let arr2 = JSON.parse(JSON.stringify(arr1));
該方法的侷限性:
1、不能存放函數或者 Undefined,否則會丟失函數或者 Undefined;
2、不要存放時間對象,否則會變成字符串形式;
3、不能存放 RegExp、Error 對象,否則會變成空對象;
4、不能存放 NaN、Infinity、-Infinity,否則會變成 null;
5、……更多請自行填坑,具體來說就是 JavaScript 和 JSON 存在差異,兩者不兼容的就會出問題。
b.使用第三方庫Lodast中的_cloneDeep(oldObj)方法;
var _ = require('lodash');
const obj1 = [
1,
'Hello!',
{ name: 'js1' },
[
{
name: 'js2',
}
],
]
const obj2 = _.cloneDeep(obj1);
c.jQuery的extend()方法
const obj1 = [
1,
'Hello!',
{ name: 'js1' },
[
{
name: 'js2',
}
],
]
const obj2 = {};
/**
* @name jQuery深拷貝
* @description $.extend(deep, target, object1, object2...)
* @param {Boolean} deep 可選 true 或者 false,默認是 false,所以一般如果需要填寫,最好是 true。
* @param {Object} target 需要存放的位置
* @param {Object} object 可以有 n 個原數據
*/
$.extend(true, obj2, obj1);