JS高級(jí)3-語言特性

一、 JS面向?qū)ο缶幊?/h2>

1昆咽、 面向?qū)ο蠼榻B

什么是對(duì)象迈套?

Everything is object (萬物皆對(duì)象), JS語言中將一切都視為 對(duì)象

  • 對(duì)象是對(duì)概念的具體化體現(xiàn):

一本書狰右、一輛汽車诡宗、一個(gè)人都可以是對(duì)象怀挠,一個(gè)數(shù)據(jù)庫乃摹、一張網(wǎng)頁云茸、一個(gè)與遠(yuǎn)程服務(wù)器的連接也可以是對(duì)象善镰。

當(dāng)實(shí)物被抽象成對(duì)象妹萨,實(shí)物之間的關(guān)系就變成了對(duì)象之間的關(guān)系,從而就可以模擬現(xiàn)實(shí)情況炫欺,針對(duì)對(duì)象進(jìn)行編程乎完。

  • 編程中對(duì)象是一個(gè)容器,封裝了屬性(property)和方法(method)

屬性是對(duì)象的狀態(tài)品洛,方法是對(duì)象的行為(完成某種任務(wù))树姨。比如,我們可以把動(dòng)物抽象為animal對(duì)象桥状,使用“屬性”記錄具體是那一種動(dòng)物帽揪,使用“方法”表示動(dòng)物的某種行為(奔跑、捕獵辅斟、休息等等)转晰。

也可以將其簡單理解為:數(shù)據(jù)集或功能集

ECMAScript-262 把對(duì)象定義為:無序?qū)傩缘募希鋵傩钥梢园局挡樾稀?duì)象或者函數(shù)蔗崎。
嚴(yán)格來講,這就相當(dāng)于說對(duì)象是一組沒有特定順序的值扰藕。對(duì)象的每個(gè)屬性或方法都有一個(gè)名字缓苛,而每個(gè)名字都
映射到一個(gè)值。

2邓深、 面向?qū)ο缶幊?/h3>

面向?qū)ο蟛皇切碌臇|西未桥,它只是過程式代碼的一種高度封裝,目的在于提高代碼的開發(fā)效率和可維護(hù)性庐完。

面向?qū)ο缶幊?—— Object Oriented Programming钢属,簡稱 OOP ,是一種編程開發(fā)思想门躯。
它將真實(shí)世界各種復(fù)雜的關(guān)系淆党,抽象為一個(gè)個(gè)對(duì)象,然后由對(duì)象之間的分工與合作讶凉,完成對(duì)真實(shí)世界的模擬染乌。

在面向?qū)ο蟪绦蜷_發(fā)思想中,每一個(gè)對(duì)象都是功能中心懂讯,具有明確分工荷憋,可以完成接受信息、處理數(shù)據(jù)褐望、發(fā)出信息等任務(wù)勒庄。
因此,面向?qū)ο缶幊叹哂徐`活瘫里、代碼可復(fù)用实蔽、高度模塊化等特點(diǎn),容易維護(hù)和開發(fā)谨读,比起由一系列函數(shù)或指令組成的傳統(tǒng)的過程式編程(procedural programming)局装,更適合多人合作的大型軟件項(xiàng)目。

面向?qū)ο笈c面向過程:

  • 面向過程就是親力親為劳殖,事無巨細(xì)铐尚,面面俱到,步步緊跟哆姻,有條不紊
  • 面向?qū)ο缶褪钦乙粋€(gè)對(duì)象宣增,指揮得結(jié)果
  • 面向?qū)ο髮?zhí)行者轉(zhuǎn)變成指揮者
  • 面向?qū)ο蟛皇敲嫦蜻^程的替代,而是面向過程的封裝

面向?qū)ο蟮奶匦裕?/p>

  • 封裝性
  • 繼承性
  • 多態(tài)性

擴(kuò)展閱讀:

3填具、 創(chuàng)建對(duì)象

JavaScript 語言的對(duì)象體系统舀,不基于“類” 創(chuàng)建對(duì)象匆骗,是基于構(gòu)造函數(shù)(constructor)和原型鏈(prototype)。

簡單方式創(chuàng)建對(duì)象

我們可以直接通過 new Object() 創(chuàng)建:

var person = new Object();
person.name = 'Jack';
person.age = 18;

person.sayName = function () {
    console.log(this.name);
}

字面量方式創(chuàng)建對(duì)象

每次創(chuàng)建通過 new Object() 比較麻煩誉简,所以可以通過它的簡寫形式對(duì)象字面量來創(chuàng)建:

var person = {
  name: 'Jack';
  age: 18;
  sayName: function () {
    console.log(this.name);
  }
}

對(duì)于上面的寫法固然沒有問題碉就,但是假如我們要生成兩個(gè) person 實(shí)例對(duì)象呢?

var person1 = {
  name: 'Jack';
  age: 18;
  sayName: function () {
    console.log(this.name);
  }
}

var person2 = {
  name: 'Mike';
  age: 16;
  sayName: function () {
    console.log(this.name);
  }
}

通過上面的代碼我們不難看出闷串,這樣寫的代碼太過冗余瓮钥,重復(fù)性太高。

簡單方式的改進(jìn):工廠函數(shù)

我們可以寫一個(gè)函數(shù)烹吵,解決代碼重復(fù)問題:

function createPerson (name, age) {
  return {
    name: name
    age: age
    sayName: function () {
      console.log(this.name);
    }
  }
}

然后生成實(shí)例對(duì)象:

var p1 = createPerson('Jack', 18);
var p2 = createPerson('Mike', 18);

這樣封裝確實(shí)爽多了碉熄,通過工廠模式我們解決了創(chuàng)建多個(gè)相似對(duì)象代碼冗余的問題,
但是這依然沒有脫離 使用 字面量方式創(chuàng)建對(duì)象 的本質(zhì)肋拔;

null 與Undefined

都表示空锈津,沒有等含義

歷史原因:

JS在最初設(shè)計(jì)時(shí)只有5中數(shù)據(jù)類型(布爾 、字符串凉蜂、浮點(diǎn)型琼梆、整形、對(duì)象)窿吩,完全沒有考慮空的情況茎杂,將空作為對(duì)象的一種;后來感覺不合理纫雁,但是由于代碼量及兼容性問題煌往,僅僅將NULL從概念上拿出來當(dāng)做獨(dú)立的數(shù)據(jù)類,但是語法層面依然是對(duì)象轧邪,對(duì)于其他情況的空刽脖,則新增了undefined數(shù)據(jù)類型,用于區(qū)分null;

在ES6第一版草案中忌愚,將null刪掉了曾棕,但是在發(fā)布正式版式又加上了;

在JS中unll與undefined出現(xiàn)情況的舉例:

null :

對(duì)象不存在 document.getElementById(‘dsf’);

原型鏈的頂層是Object菜循,獲取Object的原型的值就是null

undefined :

變量聲明沒有賦值

函數(shù)調(diào)用沒有返回值

函數(shù)調(diào)用需要實(shí)參,但是沒有傳遞實(shí)參申尤,函數(shù)內(nèi)的形參是undefined

獲取一個(gè)對(duì)象中不存在的屬性

二癌幕、構(gòu)造函數(shù)

1、 構(gòu)造函數(shù)

JavaScript 語言使用構(gòu)造函數(shù)作為對(duì)象的模板昧穿。
所謂 ”構(gòu)造函數(shù)”勺远,就是一個(gè)普通的函數(shù),只不過我們專門用它來生成對(duì)象时鸵,這樣使用的函數(shù)胶逢,就是構(gòu)造函數(shù)厅瞎;

類似與PHP中 class 類 的作用;
它提供模板初坠,描述對(duì)象的基本結(jié)構(gòu)和簸。
一個(gè)構(gòu)造函數(shù),可以生成多個(gè)對(duì)象碟刺,這些對(duì)象都有相同的結(jié)構(gòu)锁保。

function Person (name, age) {
  this.name = name;
  this.age = age;
  this.sayName = function () {
    console.log(this.name);
  }
}

var p1 = new Person('Jack', 18);
p1.sayName(); // => Jack

var p2 = new Person('Mike', 23)
p2.sayName(); // => Mike

解析 構(gòu)造函數(shù)代碼 的執(zhí)行

在上面的示例中,Person() 函數(shù)取代了 createPerson() 函數(shù)半沽,但是實(shí)現(xiàn)效果是一樣的爽柒。
這是為什么呢?

我們注意到者填,Person() 中的代碼與 createPerson() 有以下幾點(diǎn)不同之處:

  • 沒有顯示的創(chuàng)建對(duì)象
  • 直接將屬性和方法賦給了 this
  • 沒有 return 語句
  • 函數(shù)名使用的是大寫的 Person

而要?jiǎng)?chuàng)建 Person 實(shí)例浩村,則必須使用 new 操作符。

以這種方式調(diào)用構(gòu)造函數(shù)會(huì)經(jīng)歷以下 5 個(gè)步驟:

  1. 創(chuàng)建一個(gè)空對(duì)象占哟,作為將要返回的對(duì)象實(shí)例心墅。
  2. 將這個(gè)空對(duì)象的原型,指向構(gòu)造函數(shù)的prototype屬性重挑。先記住嗓化,后面講
  3. 將這個(gè)空對(duì)象賦值給函數(shù)內(nèi)部的this關(guān)鍵字。
  4. 執(zhí)行構(gòu)造函數(shù)內(nèi)部的代碼谬哀。
  5. 返回新對(duì)象
function Person (name, age) {
  // 當(dāng)使用 new 操作符調(diào)用 Person() 的時(shí)候刺覆,實(shí)際上這里會(huì)先創(chuàng)建一個(gè)對(duì)象
  // 然后讓內(nèi)部的 this 指向新創(chuàng)建的對(duì)象
  // 接下來所有針對(duì) this 的操作實(shí)際上操作的就是剛創(chuàng)建的這個(gè)對(duì)象

  this.name = name;
  this.age = age;
  this.sayName = function () {
    console.log(this.name);
  }

  // 在函數(shù)的結(jié)尾處會(huì)將 this 返回,也就是這個(gè)新對(duì)象
}

構(gòu)造函數(shù)和實(shí)例對(duì)象的關(guān)系

構(gòu)造函數(shù)是根據(jù)具體的事物抽象出來的抽象模板

實(shí)例對(duì)象是根據(jù)抽象的構(gòu)造函數(shù)模板得到的具體實(shí)例對(duì)象

實(shí)例對(duì)象由構(gòu)造函數(shù)而來史煎,一個(gè)構(gòu)造函數(shù)可以生成很多具體的實(shí)例對(duì)象谦屑,而每個(gè)實(shí)例對(duì)象都是獨(dú)一無二的;

每個(gè)對(duì)象都有一個(gè) constructor 屬性篇梭,該屬性指向創(chuàng)建該實(shí)例的構(gòu)造函數(shù)

反推出來氢橙,每一個(gè)對(duì)象都有其構(gòu)造函數(shù)

console.log(p1.constructor === Person); // => true
console.log(p2.constructor === Person); // => true
console.log(p1.constructor === p2.constructor); // => true

因此,我們可以通過實(shí)例對(duì)象的 constructor 屬性判斷實(shí)例和構(gòu)造函數(shù)之間的關(guān)系

注意:這種方式不嚴(yán)謹(jǐn)恬偷,推薦使用 instanceof 操作符悍手,后面學(xué)原型會(huì)解釋為什么

console.log(p1 instanceof Person); // => true
console.log(p2 instanceof Person); // => true

constructor 既可以判斷也可以獲取

instanceof 只能用于判斷

2、 構(gòu)造函數(shù)存在的問題

以構(gòu)造函數(shù)為模板袍患,創(chuàng)建對(duì)象坦康,對(duì)象的屬性和方法都可以在構(gòu)造函數(shù)內(nèi)部定義;

function Cat(name, color) {
  this.name = name;
  this.color = color;
  this.say = function () {
    console.log('hello' + this.name, this.color);
  };
}
var cat1 = new Cat('貓', '白色'); 
var cat2 = new Cat('貓', '黑色'); 
cat1.say();
cat2.say();

在該示例中诡延,從表面上看好像沒什么問題滞欠,但是實(shí)際上這樣做,有一個(gè)很大的弊端肆良。
那就是對(duì)于每一個(gè)實(shí)例對(duì)象筛璧, namesay 都是一模一樣的內(nèi)容逸绎,
每一次生成一個(gè)實(shí)例,都必須為重復(fù)的內(nèi)容夭谤,多占用一些內(nèi)存棺牧,如果實(shí)例對(duì)象很多,會(huì)造成極大的內(nèi)存浪費(fèi)沮翔。

對(duì)于這種問題我們可以把需要共享的函數(shù)定義到構(gòu)造函數(shù)外部:

function say(){
    console.log('hello' + this.name, this.color);
}

function Cat(name, color) {
  this.name = name;
  this.color = color;
  this.say = say;
}
var cat1 = new Cat('貓', '白色'); 
var cat2 = new Cat('貓', '黑色'); 
cat1.say();
cat2.say();

這樣確實(shí)可以了陨帆,但是如果有多個(gè)需要共享的函數(shù)的話就會(huì)造成全局命名空間及變量沖突的問題。

你肯定想到了可以把多個(gè)函數(shù)放到一個(gè)對(duì)象中用來避免全局命名空間沖突的問題:

var s = {
    sayhello:function (){
        console.log('hello'+this.name,this.color);
    },
    saycolor:function(){
        console.log('hello'+this.color);
    }
}

function Cat(name, color) {
  this.name = name;
  this.color = color;
  this.sayhello = s.sayhello;
  this.saycolor = s.saycolor;
}
var cat1 = new Cat('貓', '白色'); 
var cat2 = new Cat('貓', '黑色'); 
cat1.sayhello();
cat2.saycolor();

至此采蚀,我們利用自己的方式基本上解決了構(gòu)造函數(shù)的內(nèi)存浪費(fèi)問題疲牵。
但是代碼看起來還是那么的格格不入,那有沒有更好的方式呢榆鼠?

三纲爸、 原型

1、 構(gòu)造函數(shù)的 prototype屬性

JavaScript 的每個(gè)對(duì)象都繼承另一個(gè)父級(jí)對(duì)象妆够,父級(jí)對(duì)象稱為 原型 (prototype)對(duì)象识啦。

原型也是一個(gè)對(duì)象,原型對(duì)象上的所有屬性和方法神妹,都能被子對(duì)象 (派生對(duì)象) 共享
通過構(gòu)造函數(shù)生成實(shí)例對(duì)象時(shí)颓哮,會(huì)自動(dòng)為實(shí)例對(duì)象分配原型對(duì)象。
而每一個(gè)構(gòu)造函數(shù)都有一個(gè)prototype屬性鸵荠,這個(gè)屬性就是實(shí)例對(duì)象的原型對(duì)象冕茅。

null沒有自己的原型對(duì)象。

這也就意味著蛹找,我們可以把所有對(duì)象實(shí)例需要共享的屬性和方法直接定義在構(gòu)造函數(shù)的 prototype 屬性上姨伤,

也就是實(shí)例對(duì)象的原型對(duì)象上。

function Cat(color) {
  this.color = color;
}

Cat.prototype.name = "貓";
Cat.prototype.sayhello = function(){
    console.log('hello' + this.name, this.color);
}
Cat.prototype.saycolor = function (){
    console.log('hello' + this.color);
}

var cat1 = new Cat('白色'); 
var cat2 = new Cat('黑色'); 
cat1.sayhello();
cat2.saycolor();

這時(shí)所有實(shí)例的 name 屬性和 sayhello() 庸疾、saycolor 方法乍楚,
其實(shí)都是同一個(gè)內(nèi)存地址,指向構(gòu)造函數(shù)的 prototype 屬性届慈,因此就提高了運(yùn)行效率節(jié)省了內(nèi)存空間徒溪。

2、 構(gòu)造函數(shù)金顿、實(shí)例词渤、原型三者之間的關(guān)系

構(gòu)造函數(shù)的prototype屬性,就是由這個(gè)構(gòu)造函數(shù)new出來的所有實(shí)例對(duì)象的 原型對(duì)象

前面已經(jīng)講過串绩,每個(gè)對(duì)象都有一個(gè) constructor 屬性,該屬性指向創(chuàng)建該實(shí)例的構(gòu)造函數(shù)

對(duì)象._proto_ (兩邊都是兩個(gè)下劃線):獲取對(duì)象的原型對(duì)象;

console.log(cat1.__proto__ == Cat.prototype); // true

注意:ES6標(biāo)準(zhǔn)規(guī)定芜壁,_proto_屬性只有瀏覽器才需要部署礁凡,其他環(huán)境可以不部署,因此不建議使用

3高氮、 原型對(duì)象的獲取及修改

上節(jié)可以看到,想要獲取一個(gè)實(shí)例對(duì)象的原型對(duì)象顷牌,有兩種方式:

1:通過實(shí)例對(duì)象的構(gòu)造函數(shù)的prototype屬性獲取: 實(shí)例對(duì)象.constructor.prototype

2:通過實(shí)例對(duì)象的 _proto_ 屬性獲取: 實(shí)例對(duì)象.__proto__

而這兩種方式剪芍,我們都不建議使用:

obj.constructor.prototype在手動(dòng)改變?cè)蛯?duì)象時(shí),可能會(huì)失效窟蓝。

function P() {};
var p1 = new P();

function C() {};
// 修改構(gòu)造函數(shù)的prototype屬性的值為p1
C.prototype = p1; //也就是說,此后所有有C構(gòu)造函數(shù)得到的對(duì)象的原型對(duì)象都是p1;

var c1 = new C();

console.log(c1.constructor.prototype === p1) // false

推舉設(shè)置獲取實(shí)例對(duì)象的原型的方式

Object.getPrototypeOf(實(shí)例對(duì)象) 方法返回一個(gè)對(duì)象的原型對(duì)象苟跪。

這是獲取原型對(duì)象的標(biāo)準(zhǔn)方法亲配。

function Cat(name, color) {
    this.name = name;
}
var cat1 = new Cat('貓'); //獲取cat1對(duì)象的原型對(duì)象
var s = Object.getPrototypeOf(cat1); 
console.log(s);

Object.setPrototypeOf(實(shí)例對(duì)象,原型對(duì)象) 為現(xiàn)有對(duì)象設(shè)置原型對(duì)象
第一個(gè)是實(shí)例對(duì)象谁帕,第二個(gè)是要設(shè)置成為實(shí)例對(duì)象的原型對(duì)象的對(duì)象
這是設(shè)置原型對(duì)象的標(biāo)準(zhǔn)方法峡继。

function Cat(name) {
    this.name = name;
}
var ob = {p:'波斯'};
var cat1 = new Cat('貓'); 
//設(shè)置cat1的原型對(duì)象為ob 
Object.setPrototypeOf(cat1,ob); 
console.log(cat1.p);//cat1的原型對(duì)象中有p屬性 
console.log(Object.getPrototypeOf(cat1));
console.log(cat1.__proto__);
//注意:如果對(duì)象的原型被改變,不會(huì)影響構(gòu)造函數(shù)獲取的原型的結(jié)果
console.log(Cat.prototype == cat1.__proto__); //false

以上的兩中方法匈挖,都是在ES6新標(biāo)準(zhǔn)中添加的碾牌;

4、 原型及原型鏈

所有對(duì)象都有原型對(duì)象儡循;

function Cat(name, color) {
    this.name = name;
 }

var cat1 = new Cat('貓');

console.log(cat1.__proto__.__proto__.__proto__);

而原型對(duì)象中的屬性和方法舶吗,都可以被實(shí)例對(duì)象直接使用;

每當(dāng)代碼讀取某個(gè)對(duì)象的某個(gè)屬性時(shí)择膝,都會(huì)執(zhí)行一次搜索誓琼,目標(biāo)是具有給定名字的屬性

  • 搜索首先從對(duì)象實(shí)例本身開始
  • 如果在實(shí)例中找到了具有給定名字的屬性,則返回該屬性的值
  • 如果沒有找到调榄,則繼續(xù)搜索指針指向的原型對(duì)象踊赠,在原型對(duì)象中查找具有給定名字的屬性
  • 如果在原型對(duì)象中找到了這個(gè)屬性,則返回該屬性的值
  • 如果還是找不到每庆,就到原型的原型去找筐带,依次類推。
  • 如果直到最頂層的Object.prototype還是找不到缤灵,則返回undefined伦籍。

而這正是多個(gè)對(duì)象實(shí)例共享原型所保存的屬性和方法的基本原理。

對(duì)象的屬性和方法腮出,有可能是定義在自身內(nèi)帖鸦,也有可能是定義在它的原型對(duì)象上。
由于原型本身也是對(duì)象胚嘲,又有自己的原型作儿,所以形成了一條 原型鏈(prototype chain)。

注意馋劈,不在要原型上形成多層鏈?zhǔn)讲檎夜ッ蹋浅@速M(fèi)資源

5晾嘶、 更簡單的原型語法

我們注意到,前面例子中每添加一個(gè)屬性和方法就要敲一遍 構(gòu)造函數(shù).prototype 娶吞。
為減少不必要的輸入垒迂,更常見的做法是用一個(gè)包含所有屬性和方法的對(duì)象字面量來重寫整個(gè)原型對(duì)象:

function Person (name, age) {
  this.name = name
  this.age = age
}

Person.prototype = {
  type: 'human',
  sayHello: function () {
    console.log('我叫' + this.name + ',我今年' + this.age + '歲了')
  }
}

在該示例中妒蛇,我們將 Person.prototype 重置到了一個(gè)新的對(duì)象机断。
這樣做的好處就是為 Person.prototype 添加成員簡單了,但是也會(huì)帶來一個(gè)問題绣夺,那就是原型對(duì)象丟失了 constructor 成員(構(gòu)造函數(shù))吏奸。

所以,我們?yōu)榱吮3?constructor 的指向正確乐导,建議的寫法是:

function Person (name, age) {
  this.name = name
  this.age = age
}

Person.prototype = {
  // 將這個(gè)對(duì)象的構(gòu)造函數(shù)指向Person
  //constructor: Person, // => 手動(dòng)將 constructor 指向正確的構(gòu)造函數(shù)
  type: 'human',
  sayHello: function () {
    console.log('我叫' + this.name + '苦丁,我今年' + this.age + '歲了')
  }
}

var p = new Person();

6、 原生對(duì)象的原型

所有構(gòu)造函數(shù)都有prototype屬性物臂;

  • Object.prototype
  • Function.prototype
  • Array.prototype
  • String.prototype
  • Number.prototype
  • Date.prototype
  • ……

為內(nèi)置對(duì)象擴(kuò)展原型方法:

例:

var ar = [1,5,23,15,5];

function f(){
    var minarr = [];
    this.forEach(function(v,k){
        if(v < 10){
            minarr.push(v);
        }
    })
    return minarr;
}

Object.getPrototypeOf(ar).min10 = f;
console.log(ar.min10());//[1, 5, 5]

// 其他數(shù)組對(duì)象也具有相應(yīng)的方法
var a = [1,2,34,7];
console.log(a.min10()); //[1, 2, 7]

這種技術(shù)被稱為猴子補(bǔ)丁旺拉,并且會(huì)破壞封裝。盡管一些流行的框架(如 Prototype.js)在使用該技術(shù)棵磷,但仍然沒有足夠好的理由使用附加的非標(biāo)準(zhǔn)方法來混入內(nèi)置原型蛾狗。

7、 原型對(duì)象的問題及使用建議

性能問題:

在原型鏈上查找屬性時(shí)是比較消耗資源的仪媒,對(duì)性能有副作用沉桌,這在性能要求苛刻的情況下很重要。

另外算吩,試圖訪問不存在的屬性時(shí)會(huì)遍歷整個(gè)原型鏈留凭。

//聲明構(gòu)造函數(shù)Man
function Man(name){
    this.name = name;
    this.p = function(){
      console.log(this.name+'跑');
  }
}

var m = new Man('張三');

console.log(m.hasOwnProperty('name')); // true
console.log(m.hasOwnProperty('age')); //false

hasOwnProperty 是 JavaScript 中唯一處理屬性并且不會(huì)遍歷原型鏈的方法。

注意:檢查屬性是否undefined還不夠偎巢。該屬性可能存在蔼夜,但其值恰好設(shè)置為undefined

//聲明構(gòu)造函數(shù)Man
function Man(name){
    this.name = name;
    this.n = undefined;
    this.p = function(){
      console.log(this.name + '跑');
  }
}

var m = new Man('張三');

if(m.n == undefined){
    console.log('沒有n屬性')
}

console.log(m.hasOwnProperty('name')); // true
console.log(m.hasOwnProperty('name')); // true
console.log(m.hasOwnProperty('n')); //true

四压昼、 繼承

1求冷、 什么是繼承

  • 現(xiàn)實(shí)生活中的繼承
  • 程序中的繼承

所謂的繼承,其實(shí)就是在子類(子對(duì)象)能夠使用父類(父對(duì)象)中的屬性及方法;

賦予后輩調(diào)用祖輩資源的權(quán)限窍霞,就是繼承匠题;

2、 原型鏈繼承

//聲明構(gòu)造函數(shù)Run
function Run(){
  this.p = function(){
      console.log(this.name + '跑');
  }
}
//聲明構(gòu)造函數(shù)Man
function Man(name){
    this.name = name;
}
//設(shè)置構(gòu)造函數(shù)Man的原型為Run,實(shí)現(xiàn)繼承
Man.prototype = new Run();

var m = new Man('張三');

m.p();

但是但金,并不建議使用原型鏈繼承韭山,而且JS 中不止有原型鏈繼承,還有其他的繼承方式,后面會(huì)講到钱磅;

五巩踏、 函數(shù)進(jìn)階

1、 函數(shù)的聲明及調(diào)用

關(guān)鍵字聲明

function f1(){
    console.log('f1');
}

表達(dá)式聲明

var f2 = function(){
    console.log('f2');
}

這種寫法將一個(gè)匿名函數(shù)賦值給變量续搀。這時(shí),這個(gè)匿名函數(shù)又稱函數(shù)表達(dá)式(Function Expression)

構(gòu)造函數(shù)方式聲明

var add = new Function(
  'x',
  'y',
  'console.log( x + y )'
);
add(1,2);

上面代碼與下面的代碼等同菠净;

// 等同于
function add(x, y) {
  console.log( x + y );
}
add(1,2);

因此禁舷,我們只關(guān)注前兩種即可,后一種只做了解毅往,不建議使用牵咙,這種聲明函數(shù)的方式非常不直觀,幾乎無人使用攀唯;

那么洁桌,關(guān)鍵字聲明表達(dá)式聲明 有什么區(qū)別?

  • 關(guān)鍵字聲明必須有名字
  • 關(guān)鍵字聲明會(huì)函數(shù)提升侯嘀,在預(yù)解析階段就已創(chuàng)建另凌,聲明前后都可以調(diào)用
  • 函數(shù)表達(dá)式類似于變量賦值
  • 函數(shù)表達(dá)式?jīng)]有函數(shù)名字
  • 函數(shù)表達(dá)式?jīng)]有變量提升,在執(zhí)行階段創(chuàng)建戒幔,必須在表達(dá)式執(zhí)行之后才可以調(diào)用
// 先調(diào)用后聲明
f1();
function f1(){
    console.log('f1');
}
//由于“變量提升”吠谢,函數(shù)f被提升到了代碼頭部,也就是在調(diào)用之前已經(jīng)聲明了
//因此可以正常調(diào)用

而表達(dá)式方式:

f();
var f = function (){};
// TypeError: undefined is not a function

上面的代碼等同于下面的形式诗茎。

var f;
f();
f = function () {};

上面代碼第二行工坊,調(diào)用f的時(shí)候,f只是被聲明了敢订,還沒有被賦值王污,等于undefined,所以會(huì)報(bào)錯(cuò)楚午。

因此昭齐,如果同時(shí)采用function命令和表達(dá)式聲明同一個(gè)函數(shù),最后總是采用表達(dá)式的定義醒叁。

var f = function () {
  console.log('1');
}

function f() {
  console.log('2');
}

f() // 1

2司浪、 第一等公民

JavaScript 語言將函數(shù)看作一種 ,與其它值(數(shù)值把沼、字符串啊易、布爾值等等)地位相同。凡是可以使用值的地方饮睬,就能使用函數(shù)租谈。比如,可以把函數(shù)賦值給變量和對(duì)象的屬性,也可以當(dāng)作參數(shù)傳入其他函數(shù)割去,或者作為函數(shù)的結(jié)果返回窟却。函數(shù)只是一個(gè)可以執(zhí)行的值,此外并無特殊之處呻逆。

由于函數(shù)與其他數(shù)據(jù)類型地位平等夸赫,所以在 JavaScript 語言中又稱函數(shù)為 第一等公民

函數(shù)作為參數(shù)

function eat (callback) {
  setTimeout(function () {
    console.log('吃完了');
    callback()
  }, 1000)
}

eat(function () {
  console.log('去唱歌');
})

函數(shù)作為返回值

function f1(){
    var s = 1;
    function f2(){
        console.log(s);
    }
    return f2;
}
var f = f1();
f();// 1

JS中一切皆對(duì)象咖城,函數(shù)也是對(duì)象茬腿,后面還會(huì)講到

3、參數(shù)及返回值

(1) 參數(shù)

形參和實(shí)參

// 函數(shù)內(nèi)部是一個(gè)封閉的環(huán)境宜雀,可以通過參數(shù)的方式切平,把外部的值傳遞給函數(shù)內(nèi)部
// 帶參數(shù)的函數(shù)聲明
function 函數(shù)名(形參1, 形參2, 形參...){
  // 函數(shù)體
}

// 帶參數(shù)的函數(shù)調(diào)用
函數(shù)名(實(shí)參1, 實(shí)參2, 實(shí)參3);

解釋:

  1. 形式參數(shù):在聲明一個(gè)函數(shù)的時(shí)候,為了函數(shù)的功能更加靈活辐董,有些值是固定不了的悴品,對(duì)于這些固定不了的值。我們可以給函數(shù)設(shè)置參數(shù)简烘。這個(gè)參數(shù)沒有具體的值苔严,僅僅起到一個(gè)占位置的作用,我們通常稱之為形式參數(shù)夸研,也叫形參邦蜜。
  2. 實(shí)際參數(shù):如果函數(shù)在聲明時(shí),設(shè)置了形參亥至,那么在函數(shù)調(diào)用的時(shí)候就需要傳入對(duì)應(yīng)的參數(shù)悼沈,我們把傳入的參數(shù)叫做實(shí)際參數(shù),也叫實(shí)參姐扮。
function fn(a, b) {
  console.log(a + b);
}
var x = 5, y = 6;
fn(x,y); 
//x,y實(shí)參絮供,有具體的值。函數(shù)執(zhí)行的時(shí)候會(huì)把x,y復(fù)制一份給函數(shù)內(nèi)部的a和b茶敏,函數(shù)內(nèi)部的值是復(fù)制的新值壤靶,無法修改外部的x,y
var f = function (one) {
  console.log(one);
}
f(1, 2, 3)

由于 JavaScript 允許函數(shù)有不定數(shù)目的參數(shù),所以需要一種機(jī)制惊搏,可以在函數(shù)體內(nèi)部讀取所有參數(shù)贮乳。

arguments對(duì)象— 函數(shù)的實(shí)參參數(shù)集合

var f = function (one) {
  console.log(arguments);
  console.log(arguments[0]);
  console.log(arguments[1]);
  console.log(arguments[2]);
}

f(1, 2, 3);

rest參數(shù)

由于JavaScript函數(shù)允許接收任意個(gè)參數(shù),于是我們就不得不用arguments來獲取所有參數(shù):

// 只獲取出a,b意外的參數(shù)
function foo(a, b) {
    var i, datas = [];
    if (arguments.length > 2) {
        for (i = 2; i < arguments.length; i++) {
            datas.push(arguments[i]);
        }
    }
    console.log('a = ' + a);
    console.log('b = ' + b);
    console.log(datas);
}
foo(1,2,3,4,5);

為了獲取 除了 已定義參數(shù)a恬惯、b之外的參數(shù)向拆,我們不得不循環(huán)arguments,并且循環(huán)要從索引2開始以便排除前兩個(gè)參數(shù)酪耳,這種寫法很別扭浓恳,只是為了獲得額外的datas參數(shù),有沒有更好的方法?

ES6標(biāo)準(zhǔn)引入了rest參數(shù)颈将,上面的函數(shù)可以改寫為:

function foo(a, b, ...rest) {
    console.log('a = ' + a);
    console.log('b = ' + b);
    console.log(rest);
}

foo(1, 2, 3, 4, 5);
// 結(jié)果:
// a = 1
// b = 2
// Array [ 3, 4, 5 ]


foo(1);
// 結(jié)果:
// a = 1
// b = undefined
// Array []

rest參數(shù)只能寫在最后梢夯,前面用...標(biāo)識(shí),從運(yùn)行結(jié)果可知晴圾,傳入的參數(shù)先綁定a颂砸、b,多余的參數(shù)以數(shù)組形式交給變量rest死姚,所以沾凄,不再需要arguments我們就獲取了全部參數(shù)。

如果傳入的參數(shù)連正常定義的參數(shù)都沒填滿知允,也不要緊,rest參數(shù)會(huì)接收一個(gè)空數(shù)組(注意不是undefined)叙谨。

注意因?yàn)閞est參數(shù)是ES6新標(biāo)準(zhǔn)温鸽,所以,請(qǐng)使用新型瀏覽器手负;

(2) 返回值

當(dāng)函數(shù)執(zhí)行完的時(shí)候涤垫,并不是所有時(shí)候都要把結(jié)果打印。我們期望函數(shù)給我一些反饋(比如計(jì)算的結(jié)果返回進(jìn)行后續(xù)的運(yùn)算)竟终,這個(gè)時(shí)候可以讓函數(shù)返回一些東西蝠猬。也就是返回值。函數(shù)通過return返回一個(gè)返回值

//聲明一個(gè)帶返回值的函數(shù)
function 函數(shù)名(形參1, 形參2, 形參...){
  //函數(shù)體
  return 返回值;
}

//可以通過變量來接收這個(gè)返回值
var 變量 = 函數(shù)名(實(shí)參1, 實(shí)參2, 實(shí)參3);

函數(shù)的調(diào)用結(jié)果就是返回值统捶,因此我們可以直接對(duì)函數(shù)調(diào)用結(jié)果進(jìn)行操作榆芦。

返回值詳解
如果函數(shù)沒有顯示的使用 return語句 ,那么這個(gè)函數(shù)就沒有任何返回值喘鸟,僅僅是執(zhí)行了而已匆绣;
如果函數(shù)使用 return語句,那么跟在return后面的值什黑,就成了函數(shù)的返回值崎淳;
如果函數(shù)使用 return語句,但是return后面沒有任何值愕把,那么函數(shù)的返回值也是:undefined ,

? 函數(shù)使用return語句后拣凹,這個(gè)函數(shù)會(huì)在執(zhí)行完 return 語句之后停止并立即退出,也就是說return后面的所有其他代碼都不會(huì)再執(zhí)行恨豁。

return后沒有任何內(nèi)容嚣镜,可以當(dāng)做調(diào)試來使用;

(3) 遞歸

執(zhí)行代碼圣絮,查看執(zhí)行結(jié)果:

function fn1 () {
  console.log(111)
  fn2()
  console.log('fn1')
}

function fn2 () {
  console.log(222)
  fn3()
  console.log('fn2')
}

function fn3 () {
  console.log(333)
  fn4()
  console.log('fn3')
}

function fn4 () {
  console.log(444)
  console.log('fn4')
}

fn1()
/*
** 執(zhí)行結(jié)果為: 
111
222
333
444
fn4
fn3
fn2
fn1
*/

最簡單的一句話介紹遞歸:函數(shù)內(nèi)部自己調(diào)用自己

遞歸案例:

計(jì)算 1+2+3+......+100 的結(jié)果

function sum(n){
    if(n > 1){
        return n + sum(n - 1);
    }else{
        return 1; 
    }
}
var jieguo = sum(5);
console.log(jieguo);

遞歸必須要有判斷條件祈惶,不加判斷會(huì)死;

4、 作用域

作用域(scope)指的是變量存在的范圍捧请。在 ES5 的規(guī)范中凡涩,Javascript 只有兩種作用域:一種是全局作用域,變量在整個(gè)程序中一直存在疹蛉,所有地方都可以讀然罨;另一種是函數(shù)作用域(局部作用域)可款,變量只在函數(shù)內(nèi)部育韩。

函數(shù)外部聲明的變量就是全局變量(global variable),它可以在函數(shù)內(nèi)部讀取闺鲸。

var v = 1;

function f() {
  console.log(v);
}

f()
// 1

在函數(shù)內(nèi)部定義的變量筋讨,外部無法讀取,稱為“局部變量”(local variable)摸恍。

function f(){
  var v = 1;
}

v // ReferenceError: v is not defined

注意悉罕,對(duì)于var命令來說,局部變量只能在函數(shù)內(nèi)部聲明立镶,在其他區(qū)塊中聲明壁袄,一律都是全局變量。

(1) 作用域鏈

只有函數(shù)可以制造作用域結(jié)構(gòu)媚媒, 那么只要是代碼嗜逻,就至少有一個(gè)作用域, 即全局作用域。凡是代碼中有函數(shù)缭召,那么這個(gè)函數(shù)就構(gòu)成另一個(gè)作用域栈顷。如果函數(shù)中還有函數(shù),那么在這個(gè)作用域中就又可以誕生一個(gè)作用域嵌巷。

將這樣的所有的作用域列出來妨蛹,可以有一個(gè)結(jié)構(gòu): 函數(shù)內(nèi)指向函數(shù)外的鏈?zhǔn)浇Y(jié)構(gòu)。就稱作作用域鏈晴竞。

function f1() {
    var num = 123;
    function f2() {
        console.log( num );
    }
    f2();
}
var num = 456;
f1();

5蛙卤、 函數(shù)內(nèi)部的變量聲明的提升

與全局作用域一樣,函數(shù)作用域內(nèi)部也會(huì)產(chǎn)生“變量提升”現(xiàn)象噩死。

var命令聲明的變量颤难,不管在什么位置,變量聲明都會(huì)被提升到函數(shù)體的頭部已维。

function foo() {
    console.log(y);//undefined
    var y = 'Bob';
} 
foo();

注意: JavaScript引擎自動(dòng)提升了變量y的聲明行嗤,但不會(huì)提升變量y的賦值。

對(duì)于上述foo()函數(shù)垛耳,JavaScript引擎看到的代碼相當(dāng)于:

function foo() {
    var y; // 提升變量y的申明 
    var x = 'Hello, ' + y; 
    alert(x);
    y = 'Bob';
}

6栅屏、 函數(shù)本身的作用域

var a = 1;
var x = function () {
  console.log(a);
};
function f() {
  var a = 2;
  x();
}
f() // 1 
// 討論結(jié)果為什么是 1  飘千?

函數(shù)本身也是一個(gè)值,也有自己的作用域栈雳。
它的作用域與變量一樣护奈,就是其聲明時(shí)所在的作用域,與其運(yùn)行時(shí)所在的作用域無關(guān)哥纫。
總之霉旗,函數(shù)執(zhí)行時(shí)所在的作用域,是定義時(shí)的作用域蛀骇,而不是調(diào)用時(shí)所在的作用域厌秒。

var x = function () {
  console.log(a);
};
function y(f) {
    var a = 2;
    f(); 
}
y(x);// ReferenceError: a is not defined
function foo() {
    var x = 1;
    function bar() {
        console.log(x);
    }
    return bar; 
}
var x = 2;
var f = foo();
f() // 1

7、 閉包

(1) 關(guān)于作用域的問題

var n = 999;
function f1() { 
    console.log(n);
}
f1() // 999

函數(shù)內(nèi)部可以直接讀取全局變量擅憔,函數(shù) f1 可以讀取全局變量 n鸵闪。

但是,在函數(shù)外部無法讀取函數(shù)內(nèi)部聲明的變量暑诸。

function f1() {
  var n = 99;
}
f1()
console.log(n);

有時(shí)我們卻需要在函數(shù)外部訪問函數(shù)內(nèi)部的變量;
正常情況下恋沃,這是辦不到的宜狐,只有通過變通方法才能實(shí)現(xiàn)臭埋。
那就是在函數(shù)的內(nèi)部罢艾,再定義一個(gè)函數(shù)伞矩。

function f1() {
    var n = 999;
 
    var f2 = function() {
        console.log(n);
    } 
    return f2;
}
var f = f1();
f();

上面代碼中笛洛,函數(shù)f2就在函數(shù)f1內(nèi)部,這時(shí)f1內(nèi)部的所有局部變量乃坤,對(duì)f2都是可見的苛让。
但是反過來就不行,f2內(nèi)部的局部變量湿诊,對(duì)f1就是不可見的狱杰。

這就是JavaScript語言特有的”鏈?zhǔn)阶饔糜颉苯Y(jié)構(gòu)(chain scope),子級(jí)會(huì)一層一層地向上尋找所有父級(jí)的變量厅须。
所以仿畸,父級(jí)的所有變量,對(duì)子級(jí)都是可見的朗和,反之則不成立错沽。

既然f2可以讀取f1的局部變量,那么只要把f2作為返回值眶拉,我們不就可以在f1外部讀取它的內(nèi)部變量了嗎!

閉包就是函數(shù)f2千埃,即能夠讀取其他函數(shù)內(nèi)部變量的函數(shù)。
由于在JavaScript語言中忆植,只有函數(shù)內(nèi)部的子函數(shù)才能讀取內(nèi)部變量放可,

因此可以把閉包簡單理解成“定義在一個(gè)函數(shù)內(nèi)部的函數(shù)”谒臼。
閉包最大的特點(diǎn),就是它可以“記住”誕生的環(huán)境耀里,比如f2記住了它誕生的環(huán)境f1蜈缤,所以從f2可以得到f1的內(nèi)部變量。
在本質(zhì)上备韧,閉包就是將函數(shù)內(nèi)部和函數(shù)外部連接起來的一座橋梁;

閉包(closure)是 Javascript 語言的一個(gè)難點(diǎn)劫樟,也是它的特色,很多高級(jí)應(yīng)用都要依靠閉包實(shí)現(xiàn)织堂。

理解閉包叠艳,首先必須理解變量作用域。

(2)關(guān)于JS垃圾回收機(jī)制的問題

function f1() { 
    var n = 99;
    console.log(++n);
}
f1(); //100 
f1(); //100

當(dāng)我們?cè)诤瘮?shù)內(nèi)部引入一個(gè)變量或函數(shù)時(shí)易阳,系統(tǒng)都會(huì)開辟一塊內(nèi)存空間;還會(huì)將這塊內(nèi)存的引用計(jì)數(shù)器進(jìn)行初始化附较,初始化值為0如果外部有全局變量或程序引用了這塊空間,則引用計(jì)數(shù)器會(huì)自動(dòng)進(jìn)行+1操作當(dāng)函數(shù)執(zhí)行完畢后,變量計(jì)數(shù)器重新歸零潦俺,系統(tǒng)會(huì)運(yùn)行垃圾回收拒课,將函數(shù)運(yùn)行產(chǎn)生的數(shù)據(jù)銷毀;如計(jì)數(shù)器不是 0 ,則不會(huì)清楚數(shù)據(jù);

這個(gè)過程就稱之為 "JS的垃圾回收機(jī)制" ;

如果將上節(jié)的代碼事示,改為閉包形式:

function f1() {
    var n = 99;
    function f2(){
        console.log(++n);
    }
    return f2; 
}
var f = f1();
f(); //100
f(); //101

運(yùn)行代碼發(fā)現(xiàn)早像,函數(shù)調(diào)用一次,其變量 n 變化一次;

因 函數(shù)f1被調(diào)用時(shí)肖爵,返回的結(jié)果是f2函數(shù)體卢鹦,也就是說,f2函數(shù)被當(dāng)作值返回給f1的調(diào)用者劝堪,
但是f2函數(shù)并沒有在此時(shí)被調(diào)用執(zhí)行冀自,
所以整個(gè) f1 函數(shù)體,無法判斷子函數(shù)f2會(huì)對(duì)其產(chǎn)生何種影響秒啦,無法判斷 變量n是否會(huì)被使用;
即使f1函數(shù)被調(diào)用結(jié)束熬粗,整個(gè)f1函數(shù)始終保留在內(nèi)存中,不會(huì)被垃圾回收機(jī)制回收;

閉包的最大用處有兩個(gè)余境,一個(gè)是可以讀取函數(shù)內(nèi)部的變量驻呐,另一個(gè)就是讓這些變量始終保持在內(nèi)存中,
即閉包可以使得它誕生環(huán)境一直存在;

注意芳来,外層函數(shù)每次運(yùn)行暴氏,都會(huì)生成一個(gè)新的閉包,而這個(gè)閉包又會(huì)保留外層函數(shù)的內(nèi)部變量绣张,所以內(nèi)存消耗很大答渔。

因此不能濫用閉包,否則會(huì)造成網(wǎng)頁的性能問題侥涵。

閉包小案例:緩存隨機(jī)數(shù)

需求:獲取隨機(jī)數(shù)沼撕,隨機(jī)數(shù)一旦生成宋雏,會(huì)在程序中多次使用,所以务豺,在多次使用中不變磨总;

function f1(){
    var num = parseInt(Math.random() * 100) + 1;
    return function (){
        console.log(num);
    }
}
var f2 = f1();
f2();
f2();
f2();

閉包點(diǎn)贊案例:

<ul>
  <li><img src="images/ly.jpg" alt=""><br/><input type="button" value="贊(1)"></li>
  <li><img src="images/lyml.jpg" alt=""><br/><input type="button" value="贊(1)"></li>
  <li><img src="images/fj.jpg" alt=""><br/><input type="button" value="贊(1)"></li>
  <li><img src="images/bd.jpg" alt=""><br/><input type="button" value="贊(1)"></li>
</ul>
<script>
  function f1() {
    var value=1;
    return function () {
      this.value="贊("+(++value)+")";
    }
  }
  //獲取所有的按鈕

  var btnObjs = document.getElementsByTagName("input");
  for(var i = 0; i < btnObjs.length; i++){
    var ff = f1();
    //將 閉包函數(shù) 綁定給事件
    btnObjs[i].onclick = ff;
  }
</script>

思考以下兩段代碼的運(yùn)行結(jié)果:

var name = "The Window";
var object = {
  name: "My Object",
  getNameFunc: function () {
    return function () {
        console.log(this.name);
    };
  }
};

object.getNameFunc()() 
var name = "The Window";  
var object = {    
  name: "My Object",
  getNameFunc: function () {
    var that = this;
    return function () {
        console.log(that.name);
    };
  }
};
object.getNameFunc()(); 

通過分析代碼得知,其中的 this 是關(guān)鍵笼沥,那么this到底是什么蚪燕?

8、 this 到底是誰

(1) this的不同指向

this關(guān)鍵字是一個(gè)非常重要的語法點(diǎn)奔浅。毫不夸張地說馆纳,不理解它的含義,大部分開發(fā)任務(wù)都無法完成汹桦。

記住一點(diǎn):this不管在什么地方使用:它永遠(yuǎn)指向一個(gè)對(duì)象鲁驶。

下面是一個(gè)實(shí)際的例子。

var person = {
  name: '張三',
  describe: function () {
    console.log('姓名:' + this.name);
  }
};

person.describe()
// "姓名:張三"

上面代碼中舞骆,this.name表示name屬性所在的那個(gè)對(duì)象钥弯。由于this.namedescribe方法中調(diào)用,而describe方法所在的當(dāng)前對(duì)象是person督禽,因此this指向person脆霎,this.name就是person.name

由于對(duì)象的屬性可以賦給另一個(gè)對(duì)象狈惫,所以屬性所在的當(dāng)前對(duì)象是可變的睛蛛,即this的指向是可變的。

var A = {
  name: '張三',
  describe: function () {
    console.log('姓名:' + this.name);
  }
};

var B = {
  name: '李四'
};

B.describe = A.describe;
B.describe()
// "姓名:李四"

上面代碼中虱岂,A.describe屬性被賦給B,于是B.describe就表示describe方法所在的當(dāng)前對(duì)象B菠红,所以this.name就指向B.name第岖。

稍稍重構(gòu)這個(gè)例子,this的動(dòng)態(tài)指向就能看得更清楚试溯。

function f() {
  console.log('姓名:' + this.name);
}

var A = {
  name: '張三',
  describe: f
};

var B = {
  name: '李四',
  describe: f
};

A.describe() // "姓名:張三"
B.describe() // "姓名:李四"

上面代碼中蔑滓,函數(shù)f內(nèi)部使用了this關(guān)鍵字,隨著f所在的對(duì)象不同遇绞,this的指向也不同键袱。

只要函數(shù)被賦給另一個(gè)變量,this的指向就會(huì)變摹闽。

var A = {
  name: '張三',
  describe: function () {
    console.log('姓名:' + this.name);
  }
};

var name = '李四';
var f = A.describe;
f() // "姓名:李四"

上面代碼中蹄咖,A.describe被賦值給變量f,的內(nèi)部this就會(huì)指向f運(yùn)行時(shí)所在的對(duì)象(本例是頂層對(duì)象)付鹿。

案例:判斷數(shù)值合法性

<input type="text" name="age" size=3 onChange="validate(this, 10, 20);">

<script>
function validate(obj, x, y){
  if ((obj.value < x) || (obj.value > y)){
    console.log('合法');
  }else{
    console.log('不合法');
  }
}
</script>

上面代碼是一個(gè)文本輸入框澜汤,每當(dāng)用戶輸入一個(gè)值蚜迅,鼠標(biāo)離開焦點(diǎn)就會(huì)觸發(fā)onChange事件,并執(zhí)行validate回調(diào)函數(shù)俊抵,驗(yàn)證這個(gè)值是否在指定范圍谁不。瀏覽器會(huì)向回調(diào)函數(shù)傳入當(dāng)前對(duì)象,因此this就代表傳入當(dāng)前對(duì)象(即文本框)徽诲,就然后可以從this.value上面讀到用戶輸入的值刹帕。

JavaScript語言之中,一切皆對(duì)象谎替,運(yùn)行環(huán)境也是對(duì)象偷溺,所以函數(shù)都是在某個(gè)對(duì)象下運(yùn)行的,this就是函數(shù)運(yùn)行時(shí)所在的對(duì)象(環(huán)境)院喜。這本來并不會(huì)讓我們糊涂亡蓉,但是JavaScript支持運(yùn)行環(huán)境動(dòng)態(tài)切換,也就是說喷舀,this的指向是動(dòng)態(tài)的砍濒,沒有辦法事先確定到底指向哪個(gè)對(duì)象,這才是最初初學(xué)者感到困惑的地方硫麻。

(2) 使用場合

① 全局環(huán)境

全局環(huán)境使用this爸邢,它指的就是頂層對(duì)象window

this === window // true

function f() {
  console.log(this === window);
}
f() // true

② 構(gòu)造函數(shù)

構(gòu)造函數(shù)中的this拿愧,指的是實(shí)例對(duì)象杠河。

function F(){
    this.hello=function(){
        console.log('Hello' + this.name);
    }
}
var f1 = new F();
f1.name = '張三';
f1.hello();


var f2 = new F();
f2.name = '劉能';
f2.hello();

③ 對(duì)象的方法

方法在哪個(gè)對(duì)象下,this就指向哪個(gè)對(duì)象浇辜。

var o1 = {
    s1:'123',
    f1:function (){
        console.log(this.s1)
    }
}

var o2 = {
    s1:'456',
    f1:o1.f1
}

o2.f1();

(3) 使用this時(shí)的注意事項(xiàng)

① 避免包含多層this

var o = {
  f1: function () {
    console.log(this);
    var f2 = function () {
      console.log(this);
    }
    f2();
  }
}

o.f1()
// Object  
// Window  

如果要在內(nèi)層函數(shù)中使用外層的this指向券敌,一般的做法是:

var o = {
  f1: function () {
    console.log(this);
    var that = this;
    var f2 = function () {
      console.log(that);
    }
    f2();
  }
}

o.f1()
// Object  
// Object  

② 不在循環(huán)數(shù)組中使用this

var ar = ['a', 'b', 'c'];
ar.forEach(function(v, k, ar){
    console.log(this[k])
})

this的動(dòng)態(tài)切換,固然為JavaScript創(chuàng)造了巨大的靈活性柳洋,但也使得編程變得困難和模糊待诅。

有時(shí),需要把this固定下來熊镣,避免出現(xiàn)意想不到的情況卑雁;JavaScript提供了callapply绪囱,bind這三個(gè)方法测蹲,來切換/固定this的指向。

9鬼吵、 call()方法扣甲、apply()方法、bind()方法

call()方法

var lisi = {names:'lisi'};
var zs = {names:'zhangsan'};
function f(age){
    console.log(this.names);
    console.log(age);
    
}
f(23);//undefined
f.call(zs,32);//zhangsan

call方法使用的語法規(guī)則

函數(shù)名稱.call(obj,arg1,arg2...argN);

參數(shù)說明:

obj:函數(shù)內(nèi)this要指向的對(duì)象,

arg1,arg2...argN :參數(shù)列表齿椅,參數(shù)與參數(shù)之間使用一個(gè)逗號(hào)隔開

apply()方法

函數(shù)名稱.apply(obj,[arg1,arg2...,argN])

參數(shù)說明:

obj :this要指向的對(duì)象

[arg1,arg2...argN] : 參數(shù)列表文捶,但是要求格式為數(shù)組

var lisi = {name:'lisi'}; 
var zs = {name:'zhangsan'}; 
function f(age, sex){
    console.log(this.name + age + sex); 
}
f.apply(zs, [23, 'nan']);

bind()方法

bind方法用于將函數(shù)體內(nèi)的this綁定到具體的某個(gè)對(duì)象上

this.x = 9; 
var module = {
  x: 81,
  getX: function() { return this.x; }
};

module.getX(); // 返回 81

var retrieveX = module.getX;
retrieveX(); // 返回 9, 在這種情況下荷逞,"this"指向全局作用域

// 創(chuàng)建一個(gè)新函數(shù),將"this"綁定到module對(duì)象
// 新手可能會(huì)被全局的x變量和module里的屬性x所迷惑
var boundGetX = retrieveX.bind(module);
boundGetX(); // 返回 81

六粹排、 再談 面向?qū)ο?/h2>

1种远、 對(duì)象

(1) JS的類

在JS中,想要獲取一個(gè)對(duì)象顽耳,有多種方式:

var o1 = {} var o2 = new Object()

自定義構(gòu)造函數(shù)方式

function Point(x, y) {
  this.x = x;
  this.y = y;
  this.toString = function () {
    return this.x + ', ' + this.y;
  };
}

var p = new Point(1, 2);
console.log(p.toString());

但是上面這種使用構(gòu)造函數(shù)獲取對(duì)象的寫法跟傳統(tǒng)的面向?qū)ο笳Z言(比如 C++ 和 Java)差異很大坠敷,很容易讓新學(xué)習(xí)這門語言的程序員感到困惑。

ES6 提供了更接近傳統(tǒng)語言的寫法射富,引入了 Class(類)這個(gè)概念膝迎,作為對(duì)象的模板。

通過class關(guān)鍵字胰耗,可以定義類限次。

基本上,ES6 的class可以看作只是一個(gè)語法糖柴灯,它的絕大部分功能卖漫,ES5 都可以做到,新的class寫法只是讓對(duì)象原型的寫法更加清晰赠群、更像面向?qū)ο缶幊痰恼Z法而已羊始。

上面的代碼用 ES6 的class改寫,就是下面這樣查描。

//定義類
class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  toString() {
    return  this.x + ', ' + this.y ;
  }
}
var p = new Point(1, 2);
console.log(p.toString());

上面代碼定義了一個(gè)“類”突委,可以看到里面有一個(gè)constructor方法,這就是構(gòu)造方法(后面還會(huì)講到)冬三,而this關(guān)鍵字則代表實(shí)例對(duì)象匀油。也就是說,ES5 的構(gòu)造函數(shù)Point勾笆,對(duì)應(yīng) ES6 的類Point敌蚜。

Point類除了構(gòu)造方法,還定義了一個(gè)toString方法匠襟。注意钝侠,定義“類”的方法的時(shí)候该园,前面不需要加上function這個(gè)關(guān)鍵字酸舍,直接把函數(shù)定義放進(jìn)去了就可以了。另外里初,方法之間不需要逗號(hào)分隔啃勉,加了會(huì)報(bào)錯(cuò)。

ES6 的類双妨,完全可以看作構(gòu)造函數(shù)的另一種寫法淮阐。

使用的時(shí)候叮阅,也是直接對(duì)類使用new命令,跟構(gòu)造函數(shù)的用法完全一致泣特。

類同樣也有prototype屬性浩姥,而屬性的值依然是實(shí)例對(duì)象的原型對(duì)象;

class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  toString() {
    return  this.x + ', ' + this.y ;
  }
}
Point.prototype.toValue = function(){
    console.log('123');
}
var p = new Point(1, 2);
p.toValue();
console.log(p.toString());

(2) constructor 方法

constructor方法是類的默認(rèn)方法状您,通過new命令生成對(duì)象實(shí)例時(shí)勒叠,自動(dòng)調(diào)用該方法。一個(gè)類必須有constructor方法膏孟,如果沒有顯式定義眯分,一個(gè)空的constructor方法會(huì)被默認(rèn)添加。

class Point {
}

// 等同于
class Point {
  constructor() {}
}

類必須使用new 進(jìn)行實(shí)例化調(diào)用柒桑,否則會(huì)報(bào)錯(cuò)弊决。

而類一旦實(shí)例化,constructor()方法就會(huì)被執(zhí)行魁淳,就像 人一出生就會(huì)哭一樣飘诗;

constructor方法默認(rèn)返回實(shí)例對(duì)象(即this);

但是返回值完全可以指定返回另外一個(gè)對(duì)象先改;

var o1 = {
    f1:function(){
        console.log('f1');
    }
}
class Point{
    constructor (){
        return o1;
    }
    f2(){
        console.log('f2');
    }
}

var p = new Point();
p.f1(); // f1
p.f2(); //Uncaught TypeError: p.f2 is not a function

constructor方法默認(rèn)返回值盡量不要修改疚察,一旦修改,我們獲取的對(duì)象將脫離其原型仇奶;

(3) 變量提升

我們知道貌嫡,在JS中,不管是全局變量還是局部變量都存在變量提升的特性该溯,函數(shù)也有提升的特性岛抄,也可以先調(diào)用后聲明,構(gòu)造函數(shù)也一樣狈茉;如下面的代碼夫椭,完全沒問題:

var p = new Point();
p.f2();

function Point(){
    this.f2 = function(){
        console.log('f2');
    }
}

但是,需要注意: 類不存在變量提升(hoist)氯庆,這一點(diǎn)與 ES5 完全不同蹭秋。

new Man();
class Man{}

// Man is not defined

注意,class只是在原有面向?qū)ο蟮幕A(chǔ)上新加的關(guān)鍵字而已堤撵,本質(zhì)上依然沒有改變JS依賴原型的面向?qū)ο蠓绞剑?/p>

2仁讨、 再談繼承

(1) 原型鏈繼承的問題

//聲明構(gòu)造函數(shù)Run
function Run(){
  this.p = function(){
      console.log(this.name + '跑');
  }
}
//聲明構(gòu)造函數(shù)Man
function Man(name){
    this.name = name;
}
//設(shè)置構(gòu)造函數(shù)Man的原型為Run,實(shí)現(xiàn)繼承
Man.prototype = new Run();

var m = new Man('張三');

m.p();

// 由構(gòu)造函數(shù)獲取原型 
console.log(Man.prototype); // 函數(shù)對(duì)象 Run
//標(biāo)準(zhǔn)方法獲取對(duì)象的原型 
console.log(Object.getPrototypeOf(m)); // 函數(shù)對(duì)象 Run
//獲取對(duì)象的構(gòu)造函數(shù)
console.log(m.constructor); // Run 函數(shù)

運(yùn)行上面的代碼,我們發(fā)現(xiàn)對(duì)象 m 本來是通過構(gòu)造函數(shù)Man 得到的实昨,可是洞豁,m 對(duì)象丟失了構(gòu)造函數(shù),并且原型鏈繼承的方式,打破了原型鏈的完整性丈挟,不建議使用刁卜;

(2) 冒充方式的繼承

前面我們?cè)趯W(xué)習(xí)JS面向中的面向?qū)ο缶幊虝r(shí),談到了繼承曙咽;

所謂的繼承蛔趴,其實(shí)就是在子類(子對(duì)象)能夠使用父類(父對(duì)象)中的屬性及方法;

function f1(){
    this.color = '黑色';
    this.h = function(){
        console.log(this.color + this.sex);
    }
}

function F2(){
    this.sex = '鋁';
    this.fn = f1;
}

var b = new F2();
b.fn();
b.h();

運(yùn)行以上代碼可知,由構(gòu)造函數(shù)獲取的對(duì)象 b可以調(diào)用函數(shù)f1中的屬性及方法例朱;

有運(yùn)行結(jié)果可知夺脾,f1 函數(shù)中的this 實(shí)際指向了對(duì)象b ,對(duì)象b 實(shí)際上已經(jīng)繼承了f1

這種方式稱為 對(duì)象冒充方式繼承茉继,ES3之前的代碼中經(jīng)常會(huì)被使用咧叭,但是現(xiàn)在基本不使用了;

為什么不使用了呢烁竭?

還是要回到上面的代碼菲茬,本質(zhì)上講,我們只是改變了函數(shù)f1 中this的指向派撕,

f1中的this指向誰婉弹,誰就會(huì)繼承f1;

而call和apply就是專門用來改變 函數(shù)中this指向的;

call或apply 實(shí)現(xiàn)繼承

function Run(){
    this.p = function(){
        console.log('ppp');
    }
}
function Man(){
//將Run函數(shù)內(nèi)部的this指向Man的實(shí)例化對(duì)象;
    Run.call(this);
}
var m = new Man();
m.p();

//獲取對(duì)象的構(gòu)造函數(shù)
console.log(m.constructor); // Man 函數(shù)
// 由構(gòu)造函數(shù)獲取原型 
console.log(Man.prototype); // 函數(shù)對(duì)象 Man
//標(biāo)準(zhǔn)方法獲取對(duì)象的原型 
console.log(Object.getPrototypeOf(m)); // 函數(shù)對(duì)象 Man

call或apply 實(shí)現(xiàn)的繼承依然是使用對(duì)象冒充方式實(shí)現(xiàn)终吼, 此方式即實(shí)現(xiàn)了繼承的功能镀赌,同時(shí)也不再出現(xiàn)原型鏈繼承中出現(xiàn)的問題;

(3)Object.create() 創(chuàng)建實(shí)例對(duì)象及原型繼承

構(gòu)造函數(shù)作為模板际跪,可以生成實(shí)例對(duì)象商佛。但是,有時(shí)拿不到構(gòu)造函數(shù)姆打,只能拿到一個(gè)現(xiàn)有的對(duì)象良姆。我們希望以這個(gè)現(xiàn)有的對(duì)象作為模板,生成新的實(shí)例對(duì)象幔戏,這時(shí)就可以使用Object.create()方法玛追。

var person1 = {
  name: '張三',
  age: 38,
  greeting: function() {
    console.log('Hi! I\'m ' + this.name + '.');
  }
};

var person2 = Object.create(person1);

person2.name // 張三
person2.greeting() // Hi! I'm 張三.

console.log(Object.getPrototypeOf(person2) == person1); //true

上面代碼中,對(duì)象person1person2的原型闲延,后者繼承了前者所有的屬性和方法痊剖;

Object.create 的本質(zhì)就是創(chuàng)建對(duì)象;

var obj1 = Object.create({});
var obj2 = Object.create(Object.prototype);
var obj3 = new Object();

//下面三種方式生成的新對(duì)象是等價(jià)的垒玲。

如果想要生成一個(gè)不繼承任何屬性(比如沒有toStringvalueOf方法)的對(duì)象陆馁,可以將Object.create的參數(shù)設(shè)為null

var obj = Object.create(null);

obj.valueOf()
// TypeError: Object [object Object] has no method 'valueOf'

使用Object.create方法的時(shí)候侍匙,必須提供對(duì)象原型氮惯,即參數(shù)不能為空,或者不是對(duì)象想暗,否則會(huì)報(bào)錯(cuò)妇汗。

問題:如果想要實(shí)現(xiàn)繼承,應(yīng)該使用哪種繼承方式说莫?

具體對(duì)象實(shí)例繼承使用Object.create

構(gòu)造函數(shù)繼承使用對(duì)象冒充 call杨箭、apply

(4) Class 的繼承

class Run{
    p(){
        console.log('ppp');
    }
}
class Man extends Run{
}
var m = new Man();
m.p();

Class 可以通過extends關(guān)鍵字實(shí)現(xiàn)繼承,這比 ES5 的通過修改原型鏈實(shí)現(xiàn)繼承储狭,要清晰和方便很多互婿。

ES5 的繼承,實(shí)質(zhì)是先創(chuàng)造子類的實(shí)例對(duì)象辽狈,然后再將父類的方法添加到上面(Parent.call(this))挎挖。

ES6 的繼承機(jī)制完全不同零蓉,是先創(chuàng)造父類的實(shí)例對(duì)象,然后再用子類的構(gòu)造函數(shù)修改。

class Run{
    p(){
        console.log('ppp');
    }
}
class Man extends Run{
    // 顯式調(diào)用構(gòu)造方法 
    constructor(){}
}
var m = new Man();
m.p();
// Uncaught ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived

上面代碼中,Man繼承了父類Run蝙茶,但是子類并沒有先實(shí)例化Run,導(dǎo)致新建實(shí)例時(shí)報(bào)錯(cuò)恨锚。

class Run{
    p(){
        console.log('ppp');
    }
}
class Man extends Run{
    constructor(){
        // 調(diào)用父類實(shí)例
        super();
    }
}
var m = new Man();
m.p();

(5) 繼承總結(jié)

原型鏈繼承:

構(gòu)造函數(shù).prototype --- 會(huì)影響所有的實(shí)例對(duì)象

實(shí)例對(duì)象._proto_ -------- 只會(huì)影響當(dāng)前對(duì)象樟澜,需要重新修改constructor屬性的指向,ES6以后只在瀏覽器下部署

Object.setPrototypeOf(子對(duì)象涮阔,原型) ------ 只會(huì)影響當(dāng)前對(duì)象猜绣,需要重新修改constructor屬性的指向 (ES6的新語法)

空實(shí)例對(duì)象 = Object.create(原型) ;創(chuàng)建對(duì)象并明確指定原型

call 和apply 本質(zhì)上是改變函數(shù)內(nèi)部this 的指向敬特,看起來像繼承掰邢,但實(shí)際上不是繼承

class 的extends 實(shí)現(xiàn)繼承,這是標(biāo)準(zhǔn)繼承方式伟阔,但是只能在ES6中使用

七尸变、 綜合案例

整體思路:

先玩幾次,思考大概的實(shí)現(xiàn)思路减俏;

1:創(chuàng)建基本的靜態(tài)頁面召烂;

2:讓div動(dòng)起來

3:動(dòng)態(tài)創(chuàng)建Div

4:動(dòng)起來后,填補(bǔ)缺失的div

5:隨機(jī)創(chuàng)建黑塊

6:綁定點(diǎn)擊事件

7:點(diǎn)擊判斷輸贏

8:游戲結(jié)束后的限制處理

9:黑塊觸底的處理

10:加分

11:加速

注意變量作用域和this指向的問題

insertBefore娃承、firstChild奏夫、getComputedStyle、appendChild历筝、createElement酗昼、Math.random、Math.floor

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <style>
        #cont {
            margin-top:100px; 
            width: 400px;
            height: 400px; 
            border-top:1px solid blue; 
            position: relative; 
            /*隱藏頂部*/ 
            overflow: hidden;
        }
        #main {
            width: 400px;
            height: 400px; 
            /*去掉紅框*/
            /* border:1px solid red; */
            position: relative; 
            top:-100px;
        }
        .row {
            height: 100px;
        }
        .row div {
            width: 98px;
            height: 98px;
            border:1px solid gray;
            float: left;
        }
        .black {
            background: black;
        }
    </style>
</head>
<body>
    <input type="text" id="fen" value="0" disabled>
    <div id="cont">
        <div id="main"></div>
    </div>
</body>

<script>
    function Youxi(){
        // 獲取main節(jié)點(diǎn)對(duì)象
        this.main = document.getElementById('main');
        this.interval = ''; // 定時(shí)器
        this.over = false; // 有是否結(jié)束的標(biāo)志
        this.sudu = 1; // 初始化下降速度

        // 創(chuàng)建DIV的方法
        this.cdiv = function(classNames){
            // 創(chuàng)建一個(gè)div節(jié)點(diǎn)對(duì)象
            var div = document.createElement('div');
            // 根據(jù)傳入的值梳猪,創(chuàng)建不同class屬性的div
            if(classNames){
                div.className = classNames;
            }
            return div;
        }

        //一次生成一行div
        this.crow = function(init){
            var row = this.cdiv('row');
            // 獲取0-3的隨機(jī)數(shù)
            var k = Math.floor(Math.random() * 4)
            // 每行div根據(jù)隨機(jī)數(shù)麻削,隨機(jī)設(shè)置一個(gè)黑塊
            for(var i = 0; i < 4; i++){
                // 隨機(jī)出現(xiàn)黑塊
                if(i == k){
                    row.appendChild(this.cdiv('black'));
                }else{
                    row.appendChild(this.cdiv());
                }
            }
            return row;
        }

        // 初始化運(yùn)行
        this.init = function(){
            // 循環(huán)創(chuàng)建4行蒸痹,并添加到main中
            for(var i = 0;i < 4; i++){
                var row = this.crow();
                this.main.appendChild(row);
            }
            // 綁定點(diǎn)擊事件
            this.clicks();
            // 設(shè)置定時(shí)器,使DIV動(dòng)起來
            this.interval = window.setInterval('start.move()' , 15);
        }
        
        // 綁定點(diǎn)擊事件
        this.clicks = function(){
            // 因?yàn)樵谄渌饔糜蛑幸褂帽緦?duì)象呛哟,
            // 防止this指向沖突叠荠,將this賦值給一個(gè)新的變量,在其他作用域中使用新的變量代替this
            var that = this;
            // 為整個(gè)main綁定點(diǎn)擊事件
            this.main.onclick = function(ev){
                // 通過事件對(duì)象扫责,獲取具體點(diǎn)擊的節(jié)點(diǎn)
                var focus = ev.target;
                // 如果游戲已經(jīng)結(jié)束了
                if(that.over){
                    alert('別掙扎了榛鼎,游戲已經(jīng)結(jié)束了!');
                }
                // 如果點(diǎn)擊的元素有值為black的class屬性鳖孤,
                // 證明用戶點(diǎn)擊的是黑色塊
                else if(focus.className == 'black'){
                    // 獲取文本框節(jié)點(diǎn)對(duì)象
                    var score = document.getElementById('fen');
                    // 將文本框的值獲取并加1后重新復(fù)制
                    var sc = parseInt(score.value)+1;
                    score.value = sc;
                    // 將黑塊變白
                    focus.className = '';
                    // 如果此行被點(diǎn)擊過者娱,給這一行發(fā)一個(gè)'同行證'
                    focus.parentNode.pass = true;
                    // 得分每增加5,下降速度提高0.5個(gè)像素點(diǎn)
                    if(sc%5 == 0){
                        that.sudu += 0.5;
                    }

                }else{
                    // 點(diǎn)擊的不是黑塊苏揣,結(jié)束游戲
                    window.clearInterval(that.interval);
                    // 游戲已經(jīng)結(jié)束了
                    that.over = true;
                    alert('游戲已結(jié)束')
                }
            }
        }

        // 每調(diào)用一次 main 的top值加2像素黄鳍,main就會(huì)向下移動(dòng)2像素
        // 我們只需要不斷調(diào)用move,就會(huì)讓main不斷下降
        this.move = function(){
            // 獲取top值
            var t = getComputedStyle(this.main, null)['top'];
            var tops = parseInt(t);
            // 如果tops大于1,證明一行下降結(jié)束
            if(tops>1){
                // 如果此行沒有通行證平匈,游戲結(jié)束
                if(this.main.lastChild.pass == undefined){
                    window.clearInterval(this.interval);
                    // 游戲已經(jīng)結(jié)束了
                    this.over = true;
                    alert('游戲已結(jié)束')
                }else{ // 如果有通行證
                    // 如果大于5行际起,刪除最后一行
                    if(this.main.children.length >= 5) {
                        this.main.removeChild(this.main.lastChild);
                    }
                }
                // 下降結(jié)束一行,則在最頂部增加一行吐葱,完成下降的連續(xù)性
                var row = this.crow();
                this.main.insertBefore(row,this.main.firstChild);
                // 并重新隱藏新加的一行
                this.main.style.top = '-100px';
            }else{
                // 定時(shí)器每調(diào)用一次街望,top 值修改一次
                // 完成下降動(dòng)作
                this.main.style.top = tops + this.sudu +'px';
            }
        }
    }

    var start = new Youxi();
    start.init();
</script>
</html>

  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市弟跑,隨后出現(xiàn)的幾起案子灾前,更是在濱河造成了極大的恐慌,老刑警劉巖孟辑,帶你破解...
    沈念sama閱讀 206,482評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件哎甲,死亡現(xiàn)場離奇詭異,居然都是意外死亡饲嗽,警方通過查閱死者的電腦和手機(jī)炭玫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來貌虾,“玉大人吞加,你說我怎么就攤上這事【『荩” “怎么了衔憨?”我有些...
    開封第一講書人閱讀 152,762評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長袄膏。 經(jīng)常有香客問我践图,道長,這世上最難降的妖魔是什么沉馆? 我笑而不...
    開封第一講書人閱讀 55,273評(píng)論 1 279
  • 正文 為了忘掉前任码党,我火速辦了婚禮德崭,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘揖盘。我一直安慰自己眉厨,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評(píng)論 5 373
  • 文/花漫 我一把揭開白布扣讼。 她就那樣靜靜地躺著,像睡著了一般缨叫。 火紅的嫁衣襯著肌膚如雪椭符。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,046評(píng)論 1 285
  • 那天耻姥,我揣著相機(jī)與錄音销钝,去河邊找鬼。 笑死琐簇,一個(gè)胖子當(dāng)著我的面吹牛蒸健,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播婉商,決...
    沈念sama閱讀 38,351評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼似忧,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了丈秩?” 一聲冷哼從身側(cè)響起盯捌,我...
    開封第一講書人閱讀 36,988評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蘑秽,沒想到半個(gè)月后饺著,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,476評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡肠牲,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評(píng)論 2 324
  • 正文 我和宋清朗相戀三年幼衰,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片缀雳。...
    茶點(diǎn)故事閱讀 38,064評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡渡嚣,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出肥印,到底是詐尸還是另有隱情严拒,我是刑警寧澤,帶...
    沈念sama閱讀 33,712評(píng)論 4 323
  • 正文 年R本政府宣布竖独,位于F島的核電站裤唠,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏莹痢。R本人自食惡果不足惜种蘸,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評(píng)論 3 307
  • 文/蒙蒙 一墓赴、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧航瞭,春花似錦诫硕、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至滨彻,卻和暖如春藕届,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背亭饵。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評(píng)論 1 262
  • 我被黑心中介騙來泰國打工休偶, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人辜羊。 一個(gè)月前我還...
    沈念sama閱讀 45,511評(píng)論 2 354
  • 正文 我出身青樓踏兜,卻偏偏與公主長得像,于是被迫代替她去往敵國和親八秃。 傳聞我的和親對(duì)象是個(gè)殘疾皇子碱妆,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容