對象
JavaScript不區分類和實例的概念
創建一個name對象
var name=“小明”;
創建一個函數的時候,函數也是對象
function foo() {
return 0;
}
創建對象的兩種方式
- var obj = new object();
- var obj = {};
寫個小demo感受一下:
var test={
name:"xiaohua",
age:"18",
sex:"male",
health:100,
smoke:function(){
console.log("i am smoking")
this.health--;
console.log(this.health)
},
drink: function(){
console.log("i am drining")
this.health++;
}
}
這裏創建了一個叫test的對象,裏面裝了叫小花的人名,性別,性別。
定義了兩個函數體,分別用來控制小花的健康,抽菸呢健康就在100的基礎上-1,喝酒呢就加一。
我們在控制檯查看
對象的增刪查改
在上面的代碼上進行
”增加“
"刪除"
一定要用delete
"查找"
”改”
也就是在原有基礎上給變量賦值;
構造函數
構造函數 ,是一種特殊的方法。主要用來在創建對象時初始化對象。 即爲對象變量賦初始值。每個構造函數的實例都將共享構造函數的初始值。 構造函數的出現是爲了解決使用Object構造函數和字面量表示法不方便創建大量重複對象的問題。
構造函數的內部原理
-
在函數體前面隱式的加上this = {};
-
執行this.xxx = xxx ;
-
隱式返回this
構造函數的定義的格式:
修飾符 函數名(形式參數){
函數體...
}
- 構造函數 是沒有返回值類型的。
- 構造函數的函數名必須要與類名一致
- 構造函數需要以一個大寫字母開頭,而非構造函數應該以一個小寫字母開頭,這個主要是爲了區別構造函數和其它函數
- 構造函數其實本身也是函數,只是用來創建對象
- 調用構造函數千萬不要忘記寫new
- 如果一個類沒有顯式的寫上一個構造方法時,那麼java編譯器會爲該類添加一個無參的構造函數的。
任何函數,只要通過 new 操作符來調用,那它就可以作爲構造函數 ;
function Student(name) {
this.name = name; //只在被實例化後的實例中可調用
this.hello = function () {
alert('Hello, ' + this.name + '!');
}
}
var xiaoming = new Student('小明');//this指向小明
xiaoming.name; // '小明'
xiaoming.hello(); // Hello, 小明!
普通函數與構造函數的區別“
1. 返回值類型的區別:
1. 構造函數是沒有返回值類型 的,
2. 普通函數是有返回值類型的,即使函數沒有返回值,返回值類型也要寫上void。
2. 函數名的區別:
1. 構造函數的函數名必須要與類名一致,
2. 普通函數的函數名只要符合標識符的命名規則即可。
3. 調用方式的區別:
1. 構造函數是 在創建對象的時候由jvm調用的。
2. 普通函數是由我們使用對象調用的,一個對象可以對象多次普通 的函數,
4. 作用上的區別:
1. 構造函數 的作用用於初始化一個對象。
2. 普通函數是用於描述一類事物的公共行爲的。
包裝類
原始值沒有屬性和方法,但是如果強行添加屬性或者訪問屬性的話,系統就會新建一個包裝類,然後在包裝類上進行操作,操作完成後進行銷燬
JS的數據類型:
**值類型(基本類型)**字符串(String)、數字(Number)、布爾(Boolean)、對空(Null)、未定義(Undefined)、Symbol。
引用數據類型對象(Object)、數組(Array)、函數(Function)。
JS中只有對象纔有屬性和方法,原始值沒有屬性和方法
給基本類型添加屬性和方法:
//給字符串添加方法 要寫到對應的包裝對象的原型下才行
var str= "hello world";
//若我們想在原型上設置一個屬性long 保存字符串的長度
//var str = new String("hello world");// 1.找到基本包裝對象,創建一個和字符串值相同的對象,
//String.prototype.long=str.length; // 2.通過這個對象找到了包裝對象下的方法並調用
//str=null; // 3.這個剛創建的對象被銷燬
String.prototype.long=str.length;// 執行到這一句,同樣因爲沒有length屬性 後臺會偷偷的執行上述三步操作
console.log(str.long); //結果爲:11
var str= "hello world";
var str2="我的長度也爲11嗎?";
String.prototype.long=str.length;
console.log(str2.long); //結果爲:11
這樣因爲你是給基本類型string的原型上添加的屬性,所以任意一個字符串都可以訪問到這個屬性及此值。(所以不提倡這種做法)
原始值沒自定義有屬性和方法 不能給原始值賦值(賦值也不會報錯)
js中提供了三種特殊的引用類型(String Number Boolean)每當我們給原始值賦屬性值時 後臺自動調用包裝類轉換
String 默認有length屬性而且不可賦值
var str = 'abc';
str+=1; //str = 'abc1';
var test = typeof str;//test = 'string';
if(test.length == 6){
// var obj = new String(;'abcd1');
// obj.sing = true;
// delete obj;
test.sign= true;
}
// var obj = new String(;'abcd1');
// console.log(obj.sing);
// delete obj;
console.log(test.sign);// undefined
test是字符串string test.length == 6 就是對的, test.sign會把test調用,然後test.sign輸出true???
但是原始值是不能操作屬性的,原始值賦屬性值要調用包裝類,
test是原始值,不會有length屬性
代碼執行在test.sign處會new string(test).sign=true
進行包裝類之後就會刪除銷燬,所以到最後一句系統看test原始值沒有sign屬性,又會包裝一遍new string(test).sign。
但這次操作跟上次操作不一樣,系統執行語句進行包裝類,執行完包裝類之後就會銷燬,在執行語句又會在創建包裝類,那麼你的這個新的包裝類中並沒有sign的屬性。
因爲進行了包裝類已經將test變成一個構造對象了,打印對象中的屬性,即使我並沒有這個屬性,系統一樣不會報錯,只會提示undefined。
所以輸出undefied
var x =1,y=z=0;
function add(n){
return n =n+1;
}
y=add(x);
function add(n){
return n = n+3;
}
z=add(x);
xyz分別輸出
輸出1,4,4,
這裏存在一個函數提升,第二個add(n)覆蓋第一個add(n)
原型
- 原型是function對象的一個屬性,它定義了構造函數製造出的對象的公共先祖。
- 通過該構造函數產生的對象,可以繼承該原型的方法。
- 原型也是對象
原型的應用
提取共有屬性,避免代碼冗餘
例如
function Car(color,owner){
this.color = color;
this.owner = owner;
this.carName = "bmw";
this.height = 1400;
this.lang = 4900;
}
var car = new Car("red","bmw",1400,4900,"prof.ji");
var car1 = new Car("blue","bmw",1400,4900,"long");
在這段代碼中car和car1和carName屬性值都是相同的,我們可以把這些屬性賦給函數原型
Person.prototype = {
height:1200;
lang:4900;
carName : "bmw";
}
function Car(color,owner){
this.owner = owner;
this.color = color;
}
var car = new Car("red","prof.ji");
var car1 = new Car("blue","long");
對象修改原型的屬性時只能通過調用原型的屬性進行改變
Person.prototype.lastname = "deng";
function Person(name){
this.name = name;
}
var person = new Person("sumong");
Person.prototype.lastname = "liu"; //改變原型的屬性
原型的幾個屬性:
prototype屬性:我們只要創建了一個函數,就會根據一組特定的規則爲這個函數創建一個prototype屬性。這個屬性指向函數的原型對象。
constructor屬性:創建了一個自定義構造函數後,其原型對象默認只會取得constructor屬性。指向了構造函數。
_proto_屬性:(這是一種隱式命名規則,是系統命名的): 這個屬性指向了構造方法的原型對象。
1.prototype
Person.prototype.name="xiaohua";
function Person(){
}
var person =new Person();
var person1=new Person();
在這裏原型是Person.prototype
祖先是Person.prototype={ }
- new Person()創建多個對象person、person1,則多個對象都會同時指向Person構造函數的原型對象。
- prototype在構造函數出生的時候就已經被定義好了
- 如果我們訪問person中的一個屬性name,如果在person對象中找到,則直接返回。如果person對象中沒有找到,則直接去person對象的__proto__屬性指向的原型對象中查找
- 原型相當於函數產生對象的隱形父級,是可以訪問的
2.constructor查看對象的構造函數
任何一個prototype對象都有一個constructor屬性,指向它的構造函數。每一個實例也有一個constructor屬性,默認調用prototype對象的constructor屬性。
function Person(){
}
Car.prototype.say = "def";
function Car(){
}
//Car.prototype = {
// constructor : Person
//}
var car = new Car();
console.log(car.constructor);//function Car(){}
//console.log(car.constructor);//function Person(){}
//註釋掉的部分:可以更改對象car的構造函數,
// 它本來是function Car(){}實例化的一個對象,
// 現在Car.prototype = {constructor : Person}讓它指向構造函數function Person(){}
3._proto_,查看原型
new一個構造函數,相當於實例化一個對象,這期間其實進行了這三個步驟:
-
在構造函數的邏輯最頂端隱式的新建一個this對象,this其實不是不是一個 空對象,
var this = { proto : Person.prototype}//__proto__屬性 指向的是對象原型。 (每個對象都有__proto__屬性,該屬性指向一個對象,就是構造函數Person的原型對象(Person.prototype)) -
去調用構造函數Person,從而設置對象的屬性和方法並初始化。並返回this對象。(把this返回,這樣每一個實例化的對象就有__proto__屬性了。)
-
上面步驟完成後,這個對象就與構造函數Person再無聯繫,這個時候即使構造函數Person再加任何成員,都不再影響已經實例化了的對象了。(此時該對象有
了自己的屬性之後,同時具有了構造函數Person的原型對象的所有成員)
a.用法:
Person.prototype.name = 'abc';
function Person(){
//var this = {
// __proto__ : Person.prototype//指向的是對象原型
// }
}
var obj ={
name : "sunny"
}
var person = new Person();
person.__proto__ = obj;
console.log(person.name);
當new一個對象的時候,開始查找屬性name,
先看自己的構造函數裏面有沒有name屬性,如果有就直接用,
如果沒有就沿着this裏面__proto__ 屬性去對象的原型裏面查找,這個時候我改變了person.proto = obj;讓它指向對象obj,值就是sunny
b.’.'的寫法改變原型對象屬性的值,那麼結果也會跟着改
Person.prototype.name = 'sunny';
function Person(){
//var this = {__proto__ : Person.prototype}
}
// Person.prototype.name = 'cherry';
var person = new Person();
Person.prototype.name = 'cherry';
console.log(person.name)
無論Person.prototype.name = 'cherry’放在var person = new Person();上面還是下面,輸出都是cherry,因爲它改變的是__proto__ 指向的Person.prototype上的值。值都變了,person.name必然會變化。
c.改變原型讓它指向另外的一個空間
var obj = {name : "a"};
var obj1 = obj;//obj和obj1先指向同一個房間,
obj = {name : "b"};//obj它又指向另外一個房間
console.log(obj1.name);//a
console.log(obj.name);//b
Person.prototype.name = 'sunny';
function Person(){
//var this = {__proto__ : Person.prototype}
}
var person = new Person();
Person.prototype = { //這是把原型給改了,換了一個新對象
name : 'cherry'
}
console.log(person.name)//sunny
首先new一個對象person,調用構造函數,裏面隱式的var this = {proto : Person.prototype}
讓__proto__ 和 Person.prototype指向同一個空間,然後返回this,這個對象就構建完了。
然後Person.prototype = { name : ‘cherry’ }把自己的空間換了,但是 proto 沒有換,__proto__它還是指向原來的Person.prototype的空間值就是sunny。
那麼查找name屬性的時候,去__proto__裏面找到Person.prototype.name ,結果就是sunny.。
就像下面的過程:
Person.prototype = {name : "sunny"};
_proto_ = Person.prototype;
Person.prototype = {name : "cherry"};
console.log(_proto_.name);//sunny
Person.prototype.name = 'sunny';
function Person(){
//var this = {__proto__ : Person.prototype}
}
Person.prototype = {
name : 'cherry'
}
var person = new Person();
console.log(person.name)//cherry
這個就是Person.prototype = {name : ‘cherry’ }改變了原型對象思維指向,讓它的值爲cherry,然後再實例化一個對象的時候再調用都早函數的時候就會查找__proto__的Person.prototype爲cherry。
d,.改變原型和改變原型屬性的值綜合起來:
Person.prototype.name = 'sunny';
function Person(){
//var this = {__proto__ : Person.prototype}
}
Person.prototype = { //改變原型
name : 'cherry'
}
Person.prototype.name = 'sunny';//改變原型屬性的值
var person = new Person();
console.log(person.name)//sunny
首先是__proto__和 Person.prototype一起指向一個空間值是sunny
然後Person.prototype指向另外一個空間值是cherry,
然後再把第二次Person.prototype指向的空間的內容替換爲sunny,最後實例化對象的時候,發現自己沒有這個name屬性,
根據__proto__ 去原型裏面查找,找到的就是第二次指向的空間裏裏面的值sunny。
1.每當代碼讀取某個對象的某個屬性的時候,都會執行一次搜索。首先從對象實例本身開始,如果在實例中找到了該屬性,則返回該屬性的值,
2.如果沒有找到,則順着原型鏈指針向上,到原型對象中去找,如果如果找到就返回該屬性值。
3.如果爲對象實例添加了一個屬性,與原型中屬性同名,該屬性會屏蔽掉原型中的同名屬性
4.使用delete可以刪除自己實例中的屬性,但是原型中的屬性是刪除不了的。
原型鏈
可以把對象串聯起來的鏈式結構
function CreateDog(name,color){
this.name = name;
this.color = color;
}
CreateDog.prototype.say = function(){
console.log(this.color + this.name + '在叫!');
};
var dog = new CreateDog('薩摩耶','白色');
dog.say();
訪問原型的方法
1.通過構造函數來訪問
console.log(CreateDog.prototype);//Object { say: say(), … }
2. 通過實例化的對象來訪問
console.log(dog.proto);//Object { say: say(), … }
js 在創建對象(任何對象,普通對象和函數對象)的時候,都有一個__proto__的屬性,
這個屬性用於指向創建他的函數對象的原型對象prototype
console.log(dog.proto === CreateDog.prototype);//true
同樣的,CreateDog.prototype 對象也有一個__proto__ 指向創建他的函數的原型對象 (object)的prototype
console.log(CreateDog.prototype.proto === Object.prototype);//true
Object.prototype 也有一個__proto__ 指向null
console.log(Object.prototype.proto === null);//true
原型鏈 特點是:__proto__ 屬性
Object.prototype是原型鏈的終端
字面量創建對象的方法其實和new Object()的方法是一模一樣的。
var obj = {};
var obj1 = new Object();
它們是相等的。
obj.__proto__ ---->Object.prototype
obj1.__proto__ ---->Object.prototype
Person.prototype = {} ------->Object.prototype
function Person(){
}
函數的原型就是一個字面量的形式,所以原型鏈的終端就是Object.prototype
利用Object.create(原型)創建一個對象*
//var obj = Object.create(原型)
Person.prototype.name = "sunny";
function Person() {
}
var person = Object.create(Person.prototype);
console.log(person.name);//sunny
絕大多數對象的最終都會繼承於Object.prototype,但是也有不繼承的情況
Object.create(原型)裏面的"原型"必須是一個Object對象或者空值。(放原始值會報錯)
var obj = Object.create(null);
var obj1 = Object.create(123);//報錯
現在創建一個對象,把null放進去,發現這個對象什麼屬性都沒有了,沒原型了。
調用toString()不行,它自己沒有toString方法,它也沒原型連__proto__屬性都沒有,所以根本找不到toString方法,就會報錯。
人爲加上_proto_屬性,系統不認可
所以說是絕大多數對象繼承於Object.prototype
,不是所有的對象,因爲現在的這個Object.create(null)創建的對象根本沒有繼承屬性,連原型都沒有。
toString()方法
- undefined和null不能調用tostring方法。
var num = 123;
num.toString()
-
可以調用tostring()方法,因爲數字可以通過包裝類來一層層訪問,包裝類肯定是對象,然後對象的原型鏈的終端是Object,它有tostring方法。
-
但是undefined和null也不是對象,也沒有包裝類,他就是原始值,沒有原型,不可能可以調用tostring()方法。
數字num調用toString()方法的原理:
var num = 123;
//num.toString();------->new Number(num).toString();
//number重寫toString
Number.prototype.toString = function() {}
//Number.prototype.__proto__ = Object.prototype
//Object.prototype.toString = function () {}
首先Object.prototype上有一個toString方法,每一個繼承Object.prototype的都可以調用,但是他們自己也重寫了這個方法,就是Number.prototype.toString = function() {},每次調用toString()就是調用的自己重寫的toString()方法,方便打印出自己想要的結果。
document.write()本質上是調用tostirng方法
var obj = Object.create(null);
document.write(obj);//會報錯
document.write(obj.toString());//會報錯,因爲它沒有原型,更不會有toString()方法
-------------------------------------------------------
//現在人爲的加上toString()方法
var obj = Object.create(null);
obj.toString = function() {
return '訪問';
}
document.write(obj);//訪問
document.write(obj.toString());//訪問
//證明document.write()本質上是調用tostirng方法
call/apply
call() 會改變方法內部this的指向,指向第一個參數,後面的參數是正常傳實參。
apply()第一個參數同樣是指向的對象,但實參只能傳一個數組形式的。
即:
-
call需要把實參按照行形參的個數傳進去。
-
apply需要把實參放進一個arguments傳進去。
-
兩個方法的第一個參數必須是對象本身
/*call()方法*/
function.call(thisObj[, arg1[, arg2[, [,...argN]]]]);
call:調用一個對象的一個方法,用另一個對象替換當前對象。例如:B.call(A, args1,args2);即A對象調用B對象的方法
/*apply()方法*/
function.apply(thisObj[, argArray])
apply:調用一個對象的一個方法,用另一個對象替換當前對象。例如:B.apply(A, arguments);即A對象應用B對象的方法。
- 從定義中可以看出,call和apply都是調用一個對象的一個方法,用另一個對象替換當前對象。
- 而不同之處在於傳遞的參數,apply最多隻能有兩個參數——新this對象和一個數組argArray,如果arg不是數組則會報錯TypeError;
- apply以數組傳遞參數,call獨立傳遞。
- call則可以傳遞多個參數,第一個參數和apply一樣,是用來替換的對象,後邊是參數列表。 基本語法:
function myFunction(a, b) {
return a * b;
}
myObject = myFunction.call(myObject, 10, 2); // 返回 20
function myFunction(a, b) {
return a * b;
}
myArray = [10, 2];
myObject = myFunction.apply(myObject, myArray); // 返回 20
在 JavaScript 嚴格模式(strict mode)下, 在調用函數時第一個參數會成爲 this 的值, 即使該參數不是一個對象。
在 JavaScript 非嚴格模式(non-strict mode)下, 如果第一個參數的值是 null 或 undefined, 它將使用全局對象替代。