前端點滴(JS進階)(四)----傾盡所有
雜項
1. arguments對象
arguments對象,存在於函數的內部,它能夠表達函數的實際參數(實參),除此以外,arguments對象還有一個屬性callee,它表示函數的名字,arguments的length屬性表示實參的個數。
- 查詢形參個數:arguments.callee.length
- 查詢實參個數:arguments.length
小例子:實現實參的和
2. this 指向問題
this永遠指向一個對象。
this指向的對象是誰,關鍵要看this運行的環境。也就是this所在的函數被賦值給哪個對象了,那麼this就表示這個對象。
this 的指向
(1)全局環境下 this 表示window對象
(2)事件處理函數中的 this 指向
(3)面向對象中的 this 指向
區分this表示哪個對象,關鍵看調用函數的時候,函數是由誰來調用的。由o1調用,this就表示o1,由o2調用,this表示o2 。
(4)定時器中的 this 指向
修改 this 指向
(1)apply 函數
語法: 函數.apply(需要指向的對象,[參數1, 參數2…]);
說明:
- 參數一與參數二必須以數組類型輸入。
- 調用後會馬上執行函數
實例:
/* apply 改變this 指向 */
var obj1 = {
name:'chen'
};
var obj2 = {
name:'yaodao',
say:function(age,height){
console.log('名字爲'+this.name+'年齡'+age+'身高'+height+'cm');
}
}
obj2.say.apply(obj1,[20,167] ) //=> "名字爲chen年齡20身高167cm"
(2)call 函數
語法: 函數.call(需要指向的對象,參數1, 參數2…);
說明:
- 調用後會馬上執行函數
實例:
/* call 改變this 指向 */
var obj1 = {
name:'chen'
};
var obj2 = {
name:'yaodao',
say:function(age,height){
console.log('名字爲'+this.name+'年齡'+age+'身高'+height+'cm');
}
}
obj2.say.call(obj1,20,167) //=> "名字爲chen年齡20身高167cm"
(3)bind 函數
語法: 函數.bind(需要指向的對象,參數1, 參數2…);
說明:
- 調用後不會馬上執行函數,還需調用。
實例:
/* bind 改變this 指向 */
var obj1 = {
name:'chen'
};
var obj2 = {
name:'yaodao',
say:function(age,height){
console.log('名字爲'+this.name+'年齡'+age+'身高'+height+'cm');
}
}
obj2.say.bind(obj1,20,167)(); // 再調用一次。
/* 寫法二 */
obj2.say.bind(obj1)(20,167); //=> "名字爲chen年齡20身高167cm"
//實際上obj2.say.bind(obj1)返回改變了this指向的say方法
小實例:尋找數組的最大值並返回數組
注意: 不會改變原始數組。
var arr = [1,3,5,7,2,46,7,3,73,24];
console.log(Math.max.apply(null,arr))
JS面向對象編程特性
面向對象編程就是基於對象的編程。面向對象編程簡稱OOP(Object-Oritened Programming)爲軟件開發人員敞開了一扇大門,它使得代碼的編寫更加簡潔、高效、可讀性和維護性增強。它實現了軟件工程的三大目標:(代碼)重用性、(功能)擴展性和(操作)靈活性,它的實現是依賴於面向對象的三大特性:封裝、繼承、多態。在實際開發中 使用面向對象編程 可以實現系統化、模塊化和結構化的設計 它是每位軟件開發員不可或缺的一項技能。
1. 封裝
封裝(概念) 封裝,也就是把客觀事物封裝成抽象的類,並且類可以把自己的數據和方法只讓可信的類或者對象操作,對不可信的進行信息隱藏。封裝是面向對象的特徵之一,是對象和類概念的主要特性。 簡單的說,一個類就是一個封裝了數據以及操作這些數據的代碼的邏輯實體。在一個對象內部,某些代碼或某些數據可以是私有的,不能被外界訪問。通過這種方式,對象對內部數據提供了不同級別的保護,以防止程序中無關的部分意外的改變或錯誤的使用了對象的私有部分。
(1)es5 封裝
function Person(name,age,sex){
if (!(this instanceof Person)) {
return new Person(name, age, sex);
}
this.name = name;
this.age = age;
this.sex = sex||'female';
this.walk = function() {
if (this.age <= 2) {
return console.log('我不會走路');
}
if (this.age >2 && this.age < 4) {
return console.log('我會走路了');
}
return console.log('可以跑了');
}
this.study = function (skill) {
console.log('學習' + skill);
}
this.introduce = function () {
console.log(`我是${this.name},我是一個${this.sex === 'male' ? "男" : "女"}孩,今年${this.age}歲了。`);
}
}
// 調用方式
// new 關鍵字創建實例
var p = new Person('小明', 10, 'male');
// 直接調用創建
var p1 = Person('小紅', 9, 'female');
p.walk(); //可以跑了
p1.study('游泳'); // 學習游泳
p.introduce(); // 我是小明,我是一個男孩,今年 10 歲了。
p1.introduce(); // 我是小紅,我是一個女孩,今年 9 歲了。
使用原型鏈方式
function Person(name,age,sex){
if (!(this instanceof Person)) {
return new Person(name, age, sex);
}
this.name = name;
this.age = age;
this.sex = sex||'female';
Person.prototype.walk = function() {
if (this.age <= 2) {
return console.log('我不會走路');
}
if (this.age >2 && this.age < 4) {
return console.log('我會走路了');
}
return console.log('可以跑了');
}
Person.prototype.study = function (skill) {
console.log('學習' + skill);
}
Person.prototype.introduce = function () {
console.log(`我是${this.name},我是一個${this.sex === 'male' ? "男" : "女"}孩,今年${this.age}歲了。`);
}
}
// 調用方式
// new 關鍵字創建實例
var p = new Person('小明', 10, 'male');
// 直接調用創建
var p1 = Person('小紅', 9, 'female');
p.walk(); //可以跑了
p1.study('游泳'); // 學習游泳
p.introduce(); // 我是小明,我是一個男孩,今年 10 歲了。
p1.introduce(); // 我是小紅,我是一個女孩,今年 9 歲了。
(2)es6 封裝
特點:必須使用 new 創建實例化對象。
class Person{
/* 定義構造器 */
constructor(name,age,sex){
this.name = name;
this.age = age;
this.sex = sex||'female'
}
/* 原型 */
walk() {
if (this.age <= 2) {
return console.log('我不會走路');
}
if (this.age >2 && this.age < 4) {
return console.log('我會走路了');
}
return console.log('可以跑了');
}
study(skill) {
console.log('學習' + skill);
}
introduce() {
console.log(`我是${this.name},我是一個${this.sex === 'male' ? "男" : "女"}孩,今年${this.age}歲了。`);
}
}
// 調用方式
// new 關鍵字創建實例
var p = new Person('小明', 10, 'male');
// 直接調用創建
// var p1 = Person('小紅', 9, 'female'); // "TypeError: Class constructor Person cannot be invoked without 'new'
// 必須使用new創建實例化對象!!!!!!!!!
var p1 = new Person('小紅', 9, 'female');
p.walk(); //可以跑了
p1.study('游泳'); // 學習游泳
p.introduce(); // 我是小明,我是一個男孩,今年 10 歲了。
p1.introduce(); // 我是小紅,我是一個女孩,今年 9 歲了。
2. 繼承
繼承(概念) 繼承是指可以讓某個類型的對象獲得另一個類型的對象的屬性的方法。它支持按級分類的概念。繼承是指這樣一種能力:它可以使用現有類的所有功能,並在無需重新編寫原來的類的情況下對這些功能進行擴展。 通過繼承創建的新類稱爲“子類”或“派生類”,被繼承的類稱爲“基類”、“父類”或“超類”。繼承的過程,就是從一般到特殊的過程。要實現繼承,可以通過“繼承”( Inheritance )和“組合”( Composition )來實現。繼承概念的實現方式有二類:實現繼承與接口繼承。實現繼承是指直接使用基類的屬性和方法而無需額外編碼的能力;接口繼承是指僅使用屬性和方法的名稱、但是子類必須提供實現的能力;
(1)es5 繼承
call,apply,bind(要注意) 方法改變 this 指向實現繼承
function Person(name, sex, age) {
if (!(this instanceof Person)) {
return new Person(name, sex, age);
}
this.name = name;
this.sex = sex || 'female';
this.age = age || 0;
this.walk = function() {
if (this.age <= 2) {
return console.log('我不會走路');
}
if (this.age >2 && this.age < 4) {
return console.log('我會走路了');
}
return console.log('可以跑了');
}
this.study = function (skill) {
console.log('學習' + skill);
}
this.introduce = function () {
console.log(`我是${this.name},我是一個${this.sex === 'male' ? "男" : "女"}孩,今年${this.age}歲了。`);
}
}
function Boy(name, age) {
var obj = Person.call(this, name, 'male', age);
// var obj = Person.apply(this,[name, 'male', age])
// var obj = Person.bind(this,name, 'male', age)
obj.doHouseWork = function() {
console.log('我在做家務');
}
return obj
}
let boy = Boy('小米', 16); //let boy = Boy('小米', 16)();
boy.introduce(boy); // 我是小米,我是一個男孩,今年 16 歲了。
boy.doHouseWork();// 我在做家務
Object.creat() 實現繼承
function create(obj) {
return Object.create(obj);
}
var person = {
name: 'Tom',
age: 20,
sex: 'male',
walk: function() {
if (this.age <= 2) {
return console.log('我不會走路');
}
if (this.age >2 && this.age < 4) {
return console.log('我會走路了');
}
return console.log('走路');
},
study: function(skill) {
console.log('學習' + skill);
},
introduce: function() {
console.log(`我是${this.name},我是一個${this.sex === 'male' ? "男" : "女"}孩,今年${this.age}歲了。`);
}
};
var boy = create(person);
boy.age = 15,
boy.name = '曉東';
boy.sex = 'male';
boy.doHouseWork = function() {
console.log('我在做家務');
}
boy.introduce(); // 我是曉東,我是一個男孩,今年 15 歲了
boy.doHouseWork();// 我在做家務
原型鏈實現繼承
function Person(name, sex, age) {
if (!(this instanceof Person)) {
return new Person(name, sex, age);
}
this.name = name;
this.sex = sex || 'female';
this.age = age || 0;
Person.prototype.walk = function() {
if (this.age <= 2) {
return console.log('我不會走路');
}
if (this.age >2 && this.age < 4) {
return console.log('我會走路了');
}
return console.log('可以跑了');
}
Person.prototype.study = function (skill) {
console.log('學習' + skill);
}
Person.prototype.introduce = function () {
console.log(`我是${this.name},我是一個${this.sex === 'male' ? "男" : "女"}孩,今年${this.age}歲了。`);
}
}
function Boy(name, age) {
this.name = name;
this.age = age;
this.sex = 'male';
Boy.prototype.doHouseWork = function() {
console.log('我在做家務');
}
}
Boy.prototype = new Person(); //繼承
Boy.prototype.constructor = Boy;
var boy = new Boy('湯姆', 12);
boy.introduce(); // 我是湯姆,我是一個男孩,今年 12 歲了。
boy.doHouseWork();// 我在做家務
console.log(boy instanceof Boy);// true
深淺拷貝實現繼承
用法請看深淺拷貝實例。
(2)es6 繼承
extends super關鍵字 ——(繼承、調用)關鍵字
class Person {
/* 構造器 */
constructor(name, sex, age) {
this.name = name;
this.sex = sex;
this.age = age;
}
/* 原型 */
walk() {
if (this.age <= 2) {
return console.log('我不會走路');
}
if (this.age >2 && this.age < 4) {
return console.log('我會走路了');
}
return console.log('走路');
}
study(skill) {
console.log('學習' + skill);
}
introduce() {
console.log(`我是${this.name},我是一個${this.sex === 'male' ? "男" : "女"}孩,今年${this.age}歲了。`);
}
}
class Boy extends Person { // extends關鍵字繼承
/* 構造器 */
constructor(name, age) {
/* super語法可以調用父對象上的函數 */
super(name, 'male', age);
}
doHouseWork() {
console.log('我在做家務');
}
}
var boy = new Boy('湯姆', 14);
boy.introduce(); // 我是湯姆,我是一個男孩,今年 12 歲了。
boy.doHouseWork();// 我在做家務
console.log(boy instanceof Boy);// true
參考文章:https://www.cnblogs.com/qiqingfu/p/10238398.html
3. 多態
多態 多態就是指一個類實例的相同方法在不同情形有不同表現形式。多態機制使具有不同內部結構的對象可以共享相同的外部接口。這意味着,雖然針對不同對象的具體操作不同,但通過一個公共的類,它們(那些操作)可以通過相同的方式予以調用。
參考文章:https://segmentfault.com/a/1190000017452120
深淺拷貝
1. 爲什麼使用深淺拷貝?
因爲基本類型是不可變的,任何方法都無法改變一個基本類型的值,也不可以給基本類型添加屬性或者方法。但是可以爲引用類型添加屬性和方法,也可以刪除其屬性和方法。
基本類型和引用類型在內存中的存儲方式也大不相同,基本類型保存在棧內存中,而引用類型保存在堆內存中。爲什麼要分兩種保存方式呢? 因爲保存在棧內存的必須是大小固定的數據,引用類型的大小不固定,只能保存在堆內存中,但是我們可以把它的地址寫在棧內存中以供我們訪問。
因爲這種保存方式的存在,所以我們在操作變量的時候,如果是基本數據類型,則按值訪問,操作的就是變量保存的值;如果是引用類型的值,我們只是通過保存在變量中的引用類型的地址類操作實際對象。從而也引出了所謂的深淺拷貝問題。
/* 例子 */
let a = 10;
let obj = {
a:15
};
let a2 = a;
let obj2 = obj;
/* 對a2,obj2進行修改 */
a2 = 20;
obj2.a = 30;
console.log(a); //=> 10
console.log(obj.a); //=> 30 發現被修改了,影響了引用類型的原始值
內存分佈:
2. 淺拷貝
淺拷貝只是複製基本類型的數據或者指向某個對象的指針,而不是複製對象本身,源對象和目標對象共享同一塊內存;若對目標對象進行修改,存在源對象被篡改的可能。
淺拷貝的實現:
/* 定義一個源對象 */
let sourceObj = {
age:20,
name:'yaodao',
boolean:true,
null:null,
undefined:undefined,
threeD:[95,96,97],
say:function(language){
console.log(language)
},
otherObj:{name:"chen",age:15}
}
/* 淺拷貝 */
function copy(sourceObj){
let targetObj = Array.isArray(sourceObj) ? [] : {};
let clone;
for (var key in sourceObj) {
/* 交付地址 */
clone = sourceObj[key];
targetObj[key] = clone;
}
return targetObj;
}
let copyObj = copy(sourceObj);
/* 修改對象的參數 */
copyObj.age = 25;
copyObj.name= 'chen';
copyObj.boolean = false;
copyObj.threeD[0] = 100;
copyObj.otherObj.name= 'chen';
copyObj.otherObj.age= 20 ;
copyObj.say(Chinese) // 具有繼承的效果
console.log(copyObj)
console.log(sourceObj)
輸出結果:
內存中的存在形式:
3. 深拷貝
深拷貝能夠實現真正意義上的對象的拷貝,實現方法就是遞歸調用“淺拷貝”。深拷貝會創造一個一模一樣的對象,其內容地址是自助分配的,拷貝結束之後,內存中的值是完全相同的,但是內存地址是不一樣的,目標對象跟源對象不共享內存,修改任何一方的值,不會對另外一方造成影響。
/* 定義一個對象 */
let sourceObj = {
age:20,
name:'yaodao',
boolean:true,
null:null,
undefined:undefined,
threeD:[95,96,97],
say:function(language){
console.log(language)
},
otherObj:{name:"chen",age:15}
}
/* 深拷貝 */
function deepCopy(sourceObj){
let targetObj = Array.isArray(sourceObj) ? [] : {};
let clone;
for(var key in sourceObj){
clone = sourceObj[key];
if(typeof(clone) === 'object'){
if (clone instanceof Object) {
/* 每一個對象成員都進行拷貝,數組也是如此 */
targetObj[key] = deepCopy(clone);
} else {
targetObj[key] = clone;
}
}else if (typeof(clone) === 'function') {
/* 對函數進行拷貝 */
targetObj[key] = eval(clone);
} else {
targetObj[key] = clone;
}// 對基本類型的拷貝
}
return targetObj;
}
let copyObj = deepCopy(sourceObj);
/* 修改對象的參數 */
copyObj.age = 25;
copyObj.name= 'yaodao';
copyObj.boolean = false;
copyObj.threeD[0] = 100;
copyObj.otherObj.name= 'chen';
copyObj.otherObj.age= 20;
copyObj.say('Chinese') // 具有繼承的效果
console.log(copyObj)
console.log(sourceObj)
輸出結果:
內存中的存在形式:
爲了實現完整的功能,可以將深淺拷貝結合在一起:
如果 deep 爲 true 表示進行深拷貝,爲 false 表示進行淺拷貝。
function copy(deep /* [true || false] */, sourceObj) {
let targetObj = Array.isArray(sourceObj) ? [] : {};
let clone;
for (var key in sourceObj) {
clone = sourceObj[key];
if (deep && typeof(clone) === 'object') {
if (copy instanceof Object) {
targetObj[key] = copy(deep, copy);
} else {
targetObj[key] = clone;
}
} else if (deep && typeof(clone) === 'function') {
targetObj[key] = eval(clone.toString());
} else {
targetObj[key] = clone;
}
}
return targetObj;
}
4. 拷貝模板
(1)對象的深淺拷貝
對象與對象間的淺拷貝
/* 對象間的拷貝 */
var objA = {
name:'chen',
age:20,
say:function(){
console.log(1)
},
watch:function(){
console.log(2)
},
otherObj:{name:'yaodao',age:25}
// ...
};
var objB = {};
function copy(sObj,cObj){
var clone;
for(var key in sObj){
clone = sObj[key];
cObj[key] = clone;
}
}
copy(objA,objB);
對象與對象間的深拷貝 ----- 遞歸調用淺拷貝
上上述方法,此處省略…
對象間的深拷貝 ----- Object.create(),Object.assign()
說明:
1)Object.assign();存在兼容性問題
2)使用Object.create(),Object.assign()進行拷貝,若拷貝對象只有一級,則表示爲深拷貝,若多級則表示爲淺拷貝(從第二級開始)。
//*************************************** 深拷貝
/* Object.assign() */
/* 一級深拷貝 */
var sObj = {
name: 'chen',
age: 10
}
var cObj = Object.assign({}, sObj)
cObj.name= 'yaodao'
console.log(sObj); //=> {name: 'yaodao', age: 10}
/* Object.create() */
/* 一級深拷貝 */
function create(sObj){
return Object.create(sObj);
}
var sObj = {
name: 'chen',
age: 10
}
var cObj = create(sObj)
cObj.name= 'yaodao'
console.log(sObj); //=> {name: 'yaodao', age: 10}
//*************************************** 淺拷貝
/* Object.assign() */
/* 一級深拷貝 */
var sObj = {
name: 'chen',
age: 10,
/* 從第二級開始淺拷貝 */
otherObj:{name:'jack'}
}
var cObj = Object.assign({}, sObj)
cObj.otherObj.name= 'yaodao'
console.log(sObj); //=> {name: 'yaodao', age: 10,otherObj:{name:'yaodao'}}
/* Object.create() */
/* 一級深拷貝 */
function create(sObj){
return Object.create(sObj);
}
var sObj = {
name: 'chen',
age: 10,
/* 從第二級開始淺拷貝 */
otherObj:{name:'jack'}
}
var cObj = create(sObj)
cObj.otherObj.name= 'yaodao'
console.log(sObj); //=> {name: 'yaodao', age: 10,otherObj:{name:'yaodao'}}
對象間的深拷貝 ----- es6 對象解構
說明:
1)使用es6對象解構進行拷貝,若拷貝對象只有一級,則表示爲深拷貝,若多級則表示爲淺拷貝(從第二級開始)。
/* es6 對象解構 */
/* 一級深拷貝 */
var sObj = {
name: 'chen',
age: 10
}
var cObj = {...sObj}
cObj.name= 'yaodao'
console.log(sObj); //=> {name: 'yaodao', age: 10}
/* 多級淺拷貝 */
var sObj = {
name: 'chen',
age: 10,
/* 從第二級開始淺拷貝 */
otherObj:{name:'jack'}
}
var cObj = {...sObj}
cObj.otherObj.name= 'yaodao'
console.log(sObj); //=> {name: 'yaodao', age: 10,otherObj:{name:'yaodao'}}
(2)數組的深淺拷貝
純數組的深拷貝 ----- concat(),slice()
說明:
1)純數組表示由基本類型值組成的數組。
2)使用concat(),slice()對純數組進行拷貝時會返回一個全新的數組,不會影響原始數組所以被用作深淺拷貝。
/* concat()進行純數組深拷貝 */
let a = [1, '2'];
let b = [3, true];
let copy = a.concat(b);
a[1] = 5;
b[1] = 6;
// let copy = a.concat(b); 放到此處輸出 [1, 5, 3, 6]
console.log(copy); //=> [1, "2", 3, true]
console.log(a); //=> [1, 5]
console.log(b); //=> [3, 6]
/* slice()進行純數組深拷貝 */
let a = [1, '2'];
let b = a.slice();
a[0] = 0;
a[1] = 1;
b[0] = 10;
b[1] = 20;
console.log(a); //=> [0, 1]
console.log(b); //=> [10, 20]
混合數組的淺拷貝 ----- concat(),slice()
說明:
1)混合數組表示由基本類型值和引用類型值組成的數組。
2)使用concat(),slice()對混合數組進行拷貝時,基本類型深拷貝,引用類型淺拷貝,會影響原始數組。
/* concat()進行混合數組淺拷貝 */
let a = [1, {name: 'hh1'}];
let b = [2, {name: 'kk1'}];
let copy = a.concat(b);
copy[1].name = 'hh2';
copy[3].name = 'kk2';
console.log(copy); //=> [1, {name: 'hh2'}, 2, {name: 'kk2'}]
/* slice()進行混合數組淺拷貝 */
let a = [1, {name: 'hh1'}];
let copy = a.slice();
copy[0] = 2;
copy[1].name = 'hh2';
console.log(a); //=> [1, {name: 'hh2'}]
console.log(copy); //=> [2, {name: 'hh2'}]
數組的深拷貝 ----- es6 對象解構
說明:
1)使用es6數組解構進行拷貝,若拷貝數組是純數組,則表示爲深拷貝,若是混合數組則表示爲淺拷貝。
/* es6 數組解構 */
/* 純數組深拷貝 */
var arr = [1,2,3]
var res= [...arr]
res[0] = 10;
console.log(arr); //=> [1, 2, 3]
console.log(res); //=> [10, 2, 3]
/* 混合數組淺拷貝 */
var arr = [1,{name:'yaodao'},3,function say(){console.log(1)}]
var res= [...arr]
res[0] = 10;
res[1].name = 'chen';
console.log(arr); //=> [1, {name:'chen'}, 3, fn]
console.log(res); //=> [10, {name:'chen'}, 3,fn]
res[3]() //=> 1
(3)特殊深拷貝
JSON.parse()
說明:
1)用 JSON.stringify() 把對象轉成字符串,再用 JSON.parse() 把字符串轉成新的對象,可以實現對象,純數組,混合數組的深複製。
2)缺點:function 沒辦法轉成 JSON,也就是說使用此方法拷貝不了函數。
let sourceObj = {
age:20,
name:'yaodao',
boolean:true,
null:null,
undefined:undefined,
threeD:[95,96,97],
say:function(){
console.log(1);
},
otherObj:{name:"chen",age:15}
}
let copy = JSON.parse(JSON.stringify(sourceObj));
copy.age = 30;
copy.otherObj.age = 25;
console.log(sourceObj);
console.log(copy);
輸出: