面向對象編程 和 函數 - 復習

2018-6-28復習
每一次回頭看基礎,都有新的理解

Function.prototype.call

函數實例的call方法,可以指定函數內部this的指向(即:函數執行時所在的作用域,--- 函數運行時所在的對象 ),然后在所指定的作用域中,調用該函數。
注意: call方法是函數的方法
應用:

  1. 將 ( 類似數組的對象 ) 轉換成 ( 數組 )

(1)
[].slice.call(arguments)
等于 Array.prototype.slice.call(arguments)
等于 Array.from(arguments)
--- [].slice.call(arguments, x,y)參數x,y是slice()方法執行時傳入的參數



(2)
slice()方法 ----- 截取數組的一部分,返回截取部分的新數組,不改變原數組
slice(start, end)方法,截取數組的一部分,start是啟始位置,end是終止位置(不包含)
數組的slice()方法不加參數時,等于數組的拷貝



實例:

Array.prototype.slice.call({ 0: 'a', 1: 'b', length: 2 }) // ['a', 'b']
--- 將slice()方法中的this綁定在參數對象上,并在參數對象的作用域內執行slice()方法

函數實例的call方法,可以指定函數內部this的指向(即:函數執行時所在的作用域,--- 函數運行時所在的對象 ),然后在所指定的作用域中,調用該函數。

  • 函數實例的call方法,可以指定函數內部this的指向,指定函數運行時所在的對象,然后在指定的作用域中,執行該函數。
  • call方法會執行函數,無需手動調用
  • call方法的參數是一個對象,如果參數是空,null,undefined,則默認傳入全局對象window
  • call方法可以接受多個參數 :
    第一個參數是this所要指向的對象。
    后面的參數是函數調用時,所需的參數
var obj = {};

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

f() === window // true
f.call(obj) === obj // true    


--- 把f函數的this綁定在obj對象上,并在obj的作用域中,執行f函數
var n = 123;
var obj = { n: 456 };

function a() {
  console.log(this.n);
}

a.call() // 123
a.call(null) // 123
a.call(undefined) // 123
a.call(window) // 123
a.call(obj) // 456


--- 參數是null,空,undefined時,默認傳入的是全局對象 window

Function.prototype.apply()

apply方法的作用與call方法類似,也是改變this指向,然后再調用該函數。唯一的區別就是,它接收一個數組作為函數執行時的參數

  • apply()方法也是改變方法中this的執行,和call()相同
  • apply() 與 call() 方法不同的是,apply接收的參數是一個數組
  • apply()當第一個參數是null,空,undefined時,默認傳入的是全局對象window
func.apply(ojb, [arg1, arg2, ...])


apply()方法:
第一個參數,是this所要綁定的對象,this所要指向的對象
第二個參數是一個數組,會將成員依次作為函數調用時的參數
function f(x, y){
  console.log(x + y);
}

f.call(null, 1, 1) // 2
f.apply(null, [1, 1]) // 2

解析:
f()函數,本來是接受兩個參數,但在使用apply方法后,就可以變成,接受一個數組為參數

應用:

  1. 找出數組中最大的數---(或者最小的數)
Math.max(1,2,5,3) // 5
Math.min(1,2,3) // 1



const arr = [1,6,2,3,4,5];
Math.max.apply(null, arr) // 6   將max方法中的this指向頂層對象,參數是arr數組
等于:Math.max.apply(undefined, arr)
等于:Math.max([...arr])
  1. 將數組的 ( 空元素 ) 變成 ( undefined )
  • 通過apply方法,利用Array構造函數將數組的空元素變成undefined。
  • 空元素與undefined的差別在于,數組的forEach方法會跳過空元素,但是不會跳過undefined。因此,遍歷內部元素的時候,會得到不同的結果。
Array.apply(null, ['a', ,'b'])
等于: [...['a', , 'b']]


// [ 'a', undefined, 'b' ]
  1. 轉換類似數組的對象
  • 現在有了展開運算符,apply方法的各種運用都可以用展開運算符或者其他es6語法代替

Function.prototype.bind()

bind方法用于將函數體內的this綁定到某個對象,然后返回一個新函數。

  • bind方法不主動執行函數
  • call方法apply方法都在綁定函數this指向的對象后,在指向對象的作用域內主動執行該函數
  • bind方法的參數,就是所要綁定的this對象
  • bind可以接收多個參數,第一個參數是要綁定的this的對象,其他參數是原函數的參數
  • 如果bind方法的第一個參數是null或undefined,等于將this綁定到全局對象,函數運行時this指向頂層對象(瀏覽器為window)
var counter = {
  count: 0,
  inc: function () {
    this.count++;
  }
};
var func = counter.inc.bind(counter);
func();
counter.count // 1


解析:
1. 
將counter.inc方法中的this綁定在counter對象上
如果不綁定,var func = counter.inc將該方法賦值給變量后,this指向了頂層對象window
var add = function (x, y) {
  return x * this.m + y * this.n;
}

var obj = {
  m: 2,
  n: 2
};

var newAdd = add.bind(obj, 5);   // add函數的第一個參數
newAdd(5) // 20                  // add函數的第二個參數

bind函數注意點

  • bind方法每運行一次,就返回一個新函數。
  • 結合回調函數使用 -------------( 重要 )

回調函數是 JavaScript 最常用的模式之一,但是一個常見的錯誤是,將包含this的方法直接當作回調函數。解決方法就是使用bind方法,將counter.inc綁定counter。

  • 不能將含有this的方法直接當做回調函數。而應該用bind方法,將this綁定給原對象上
var counter = {
  count: 0,
  inc: function () {
    'use strict';
    this.count++;
  }
};

function callIt(callback) {
  callback();
}

callIt(counter.inc.bind(counter));
counter.count // 1

構造函數的缺點

同一個構造函數的多個實例之間,無法共享屬性,從而造成對系統資源的浪費。

  • 構造函數內部,通過this定義的屬性,在使用new命令生成實例對象的時候,這些屬性會定義在實例對象上,即 hasOwnProperty 為 true ---------- ( 重要 )
function Cat(name, color) {
  this.name = name;
  this.color = color;
  this.meow = function () {
    console.log('喵喵');
  };
}
var cat1 = new Cat('大毛', '白色');
var cat2 = new Cat('二毛', '黑色');
cat1.meow === cat2.meow  --- 不相等,函數是引用類型的數據,說明無法共享屬性
// false



obj.hasOwnProperty(prop) --- 表示:prop是否是obj的自身屬性,不含繼承屬性
cat1.hasOwnProperty('name'); // true



總結:
1. 構造函數內部定義的屬性,在生成實例對象的時候,會成為實例對象自身的屬性。
2. 構造函數各個實例之間無法共享屬性,造成系統資源浪費
3. 解決辦法是 原型對象

prototype原型對象

原型對象的所有屬性和方法,都能被實例對象所共享。

  • 也就是說,如果屬性和方法定義在原型對象上,那么所有實例對象就能共享這些屬性和方法
  • 構造函數生成的實例之間無法共享構造函數的屬性和方法,因為這些屬性和方法是生成在實例上的
  • 對于構造函數來說,prototype屬性會在生成實例對象的時候,成為實例對象的原型對象(那么原型對象的屬性和方法就能被實例對象所共享)
  • 原型對象的屬性,不是實例對象自身的屬性。修改原型對象,變動就立刻體現在所有實例對象上
  • 當實例對象本身沒有某個屬性和方法時,會在原型對象上尋找該屬性和方法,沒找到,在去原型的原型上找。直到Object.prototype上都沒找到,就返回undefined

總結:
原型對象的作用就是定義: 所有實例對象所共享的屬性和方法

function Animal(name) {
  this.name = name;
}
Animal.prototype.color = 'white';  
// 構造函數的prototype屬性在生成實例對象時,會成為實例的原型對象
// prototype對象上的color屬性,會被實例共享
var cat1 = new Animal('大毛');
var cat2 = new Animal('二毛');

cat1.color // 'white'
cat2.color // 'white'

原型鏈

所有對象都有自己原型對象

  • 所有對象都繼承了Object.prototype對象的屬性和方法,這就是所有對象都具有valueOf方法 和toString方法的原因,都繼承自Object.prototype對象
  • Object.prototype 的原型是 null, null沒有任何屬性和方法,也沒有自己的原型,原型鏈到此終結,原型鏈的盡頭是null。
  • Object.getPrototypeOf() 返回參數對象的原型對象 --------( 重要 )
  • 如果自身和原型上都定義了同名的屬性,則優先讀取自身屬性。'overriding'
Object.getPrototypeOf(Object.prototype)

Object.getPrototypeOf() --- 返回參數對象的原型對象

// null
// 表示Object.prototype對象的原型對象是null
var MyArray = function () {};

MyArray.prototype = new Array();
MyArray.prototype.constructor = MyArray;

var mine = new MyArray();    
mine.push(1, 2, 3);
mine.length // 3
mine instanceof Array // true



解析:
1. mine 是構造函數MyArray 生成實例對象
2. 構造函數的 MyArray.prototype對象成為實例對象的原型對象
3. MyArray.prototype指向了數組實例,那么MyArray的實例就具有 數組的屬性和方法
4. 所有mine具有數組的屬性和方法
5. instanceof運算符    左邊是實例   右邊是構造函數    返回布爾值

constructor屬性

prototype對象有一個constructor屬性,默認指向 prototype對象所在的構造函數。---------------------------------------------------- ( 重要 )

  • constructor屬性定義在prototype對象上,就意味著可以被所有實例所繼承(prototype對象是實例對象的原型對象, 實例對象繼承原型對象的屬性和方法)
  • constructor屬性的作用,用來確定實例對象由哪個構造函數生成
  • 利用constructor屬性可以從一個實例對象新建另一個實例對象
  • constructor屬性表示原型對象與構造函數之間的關聯關系,如果修改了原型對象,一般會同時修改constructor屬性,防止引用的時候出錯。
( 重要 )


function P() {}
P.prototype.constructor === P // true


解析:
1. P構造函數(首字母大寫)的prototype屬性是一個對象
2. prototype對象的constructor屬性指向prototype對象所在的構造函數
3. P.prototype.constructor === P 布爾值就是true
( 重要 )


function P() {}
var p = new P();
p.constructor === P // true
p.constructor === P.prototype.constructor // true
p.hasOwnProperty('constructor') // false  是繼承的,不是自身的屬性


解析:
1. prototype對象的constructor屬性,指向prototype對象所在構造函數
2. constructor是實例對象的原型對象的屬性,會被實例所繼承
   所以 
   實例p繼承了constructor屬性,p.constructor = P = P.prototype.constructor

利用constructor屬性,可以從一個實例生成另一個實例
因為: constructor屬性指向的就是構造函數




function Constr() {}
var x = new Constr();
var y = new x.constructor();   //注意x.constructor === Constr,這里要執行
// x.constructor間接調用構造函數
y instanceof Constr // true
  • constructor屬性表示原型對象與構造函數之間的關聯關系,如果修改了原型對象,一般會同時修改constructor屬性,防止引用的時候出錯。
( 重要 )


function Person(name) {
  this.name = name;
}
Person.prototype.constructor === Person // true
Person.prototype = {
  method: function () {}
};
Person.prototype.constructor === Person // false
Person.prototype.constructor === Object // true


解析:
1. Person.prototype 修改成了對象
2. 構造函數Person的原型對象改了,但沒改constructor屬性,導致這個屬性不再指向Person
3. 對象的構造函數是Object構造函數
4. 所以修改原型對象時,一般要同時修改constructor屬性的指向

instanceof運算符

instanceof運算符返回一個布爾值,表示對象是否為某個構造函數的實例。

  • instanceof運算符返回的是boolean值
  • 表示 ( 對象 ) 是否是 ( 構造函數 ) 的 ( 實例 )
  • instance:是實例的意思
  • ( instanceof ) 運算符 ( 左邊是實例對象 ),( 右邊是構造函數 )。
  • 它會檢查右邊構造函數的原型對象prototype, 是否在左邊對象的原型鏈上。
  • 由于instanceof檢查整個原型鏈,因此同一個實例對象,可能會對多個構造函數都返回true。
  • instanceof的原理是檢查右邊構造函數的prototype屬性,是否在左邊對象的原型鏈上。
  • 有一種特殊情況,就是左邊對象的原型鏈上,只有null對象。這時,instanceof判斷會失真。
  • instanceof運算符的一個用處,是判斷值的類型。
( 重要 )


v instanceof Vehicle       --------------  v是否是Vehicle的實例
// 等同于
Vehicle.prototype.isPrototypeOf(v)  -----  Vehicle.prototype是否是v的原型


解析:
1. isPrototypeOf() 方法允許你檢查一個對象是否存在于另一個對象的原型鏈上
2. isPrototypeOf()返回的是布爾值
  • instanceof的原理是檢查右邊構造函數的prototype屬性,是否在左邊對象的原型鏈上。有一種特殊情況,就是左邊對象的原型鏈上,只有null對象。這時,instanceof判斷會失真。
( 重要 )


var obj = Object.create(null);
typeof obj // "object"
Object.create(null) instanceof Object // false


解析:
1. Object.create()
   該方法接受一個對象作為參數,然后以它為原型,返回一個實例對象。
   該實例完全繼承原型對象的屬性。
   即:object.create()方法  以參數對象為原型,生成實例對象
2. instanceof的原理是檢查右邊構造函數的prototype對象是否在左邊對象的原型鏈上
3. Object.create(null) instanceof Object
    因為右邊 Object.prototype === null,不在左邊對象的原型鏈上
    左邊對象的原型鏈在Object.prototype對象上終止

instanceof 用來判斷值得類型

但是注意:instanceof 運算符只能用于對象,不適用原始類型的值。

此外,對于 undefined 和 null ,instanceOf 運算符總是返回 false。



var x = [1, 2, 3];
var y = {};
x instanceof Array // true
y instanceof Object // true
var F = function () {};
var f = new F();
Object.getPrototypeOf(f) === F.prototype // true
undefined instanceof Object // false
null instanceof Object // false

Object.getPrototypeOf()

Object.getPrototypeOf() 返回參數對象的原型,是獲取原型對象的標準方法

( 重要 )


var F = function () {};
var f = new F();
Object.getPrototypeOf(f) === F.prototype // true


解析:
1. Object.getPrototypeOf() 返回參數對象的原型
2. f 的原型是 構造函數F的 prototype屬性

幾種特殊對象的原型


1. 空對象的原型是 Object.prototype

2. Object.prototype 的原型是 null

3. 函數的原型是 Function.prototype

Object.setPrototypeOf() --- 注意 返回的是參數對象

Object.setPrototypeOf方法為參數對象設置原型,返回該參數對象。它接受兩個參數,第一個是現有對象,第二個是原型對象。

  • Object.setPrototypeOf() 返回該參數對象。
  • Object.setPrototypeOf() 接受兩個參數,第一個是現有對象(參數對象),第二個是原型對象
  • Object.setPrototypeOf() 為參數對象設置原型
( 重要 )


var a = {};
var b = {x: 1};
Object.setPrototypeOf(a, b);    // {}
Object.getPrototypeOf(a) === b // true
a.x // 1


解析:
1. Object.setPrototypeOf(參數對象,原型對象)  ---  返回的是參數對象
2. Object.setPrototypeOf()方法的作用是: 把第二個參數設置成第一個參數的原型
  • new命令可以使用Object.setPrototypeOf方法模擬。
( 重要 )  --- new命令的原理



var F = function () {
  this.foo = 'bar';
};
var f = new F();
// 等同于
var f = Object.setPrototypeOf({}, F.prototype);  --- 將空對象的原型設置成 F的prototype屬性
F.call(f);   --------------------------------------- 將F中的this指向 空對象,并執行構造函數

Object.create() --- 返回參數對象的實例對象

Object.create()接受一個對象作為參數,然后以它為原型,返回一個實例對象。該實例完全繼承原型對象的屬性。

  • Object.create() 以參數對象為原型,返回實例對象。
// 原型對象
var A = {
  print: function () {
    console.log('hello');
  }
};

// 實例對象
var B = Object.create(A);

Object.getPrototypeOf(B) === A // true
B.print() // hello
B.print === A.print // true
  • 下面三種方式生成的新對象是等價的。
var obj1 = Object.create({});
var obj2 = Object.create(Object.prototype);
var obj3 = new Object();
  • 如果想要生成一個不繼承任何屬性(比如沒有toString和valueOf方法)的對象,可以將Object.create的參數設為null。
var obj = Object.create(null);

obj.valueOf()
// TypeError: Object [object Object] has no method 'valueOf
  • 使用Object.create方法的時候,必須提供對象原型,如果參數為空,或者不是對象,都會報錯。
Object.create()
// TypeError: Object prototype may only be an Object or null
Object.create(123)
// TypeError: Object prototype may only be an Object or null
  • Object.create方法生成的新對象,動態繼承了原型。在原型上添加或修改任何方法,會立刻反映在新對象之上。
var obj1 = { p: 1 };
var obj2 = Object.create(obj1);

obj1.p = 2;
obj2.p // 2
  • Object.create方法生成的對象,繼承了它的原型對象的構造函數。
( 重要 )


function A() {}
var a = new A();
var b = Object.create(a);
b.constructor === A // true
b instanceof A // true


解析:
1. Object.create()方法生成的對象,繼承了它的原型對象的構造函數
2. var b = Object.create(a); ---- b的原型是a,  b繼承了a的構造函數
   所以 b.constructor === A // true
   所以 b instanceof A // true

Object.prototype.isPrototypeOf() - 返回布爾值

isPrototypeOf方法被實例對象所繼承

實例對象的 isPrototypeOf() 方法,用來判斷該對象是否是參數對象的原型

  • 只要實例對象處在參數對象的原型鏈上, isPrototypeOf 方法就返回true

Object.prototype.__proto__

  • 實例對象的__proto__返回該實例對象的原型
  • __proto__該屬性可讀寫
  • 只需要瀏覽器才需要部署該屬性,是一個內部屬性
  • 盡量少用__proto__屬性,而是使用
    Object.getPrototypeOf()讀參數對象的原型
    Object.setPrototypeOf()寫第一個參數對象的原型為第二個參數對象
componentDidMount() {
   const obj1 = {};
   const obj2 = {'name': 'wang'};
   obj1.__proto__ = obj2;
   console.log(obj2.isPrototypeOf(obj1), '實例對象的isPrototypeOf()放回一個布爾值,表示實例對象是否是參數對象的原型');
   console.log(Object.getPrototypeOf(obj1), 'getPrototypeOf()方法,返回參數對象的原型對象');
}

獲取原型對象的方法比較

獲取實例對象的原型有三種方法: Object.getPrototypeOf()勝出

  1. obj.__proto__實例對象繼承的 __proto__屬性
  2. obj.constructor.prototype
    ----- 實例的constructor是繼承自實例構造函數的 prototype上的constructor
  3. Object.getPrototypeOf()
(重要)  -- 比較三種獲取原型的方法



1. __proto__ 只在瀏覽器上有     ---- 不靠譜
2. obj.constructor.prototype在手動修改原型時,還要修改構造函數,否則可能會失效



結論:使用Object.getPrototypeOf() 最好

Object.prototype.hasOwnProperty

判斷某個屬性,是在對象自身屬性還是繼承屬性

  • 實例對象的 hasOwnProperty 屬性返回一個布爾值,用于判斷某個屬性定義在對象自身,還是定義在原型鏈上
  • hasOwnProperty是js中唯一一個處理對象屬性時,不會遍歷原型鏈的方法

in運算符

in運算符返回一個布爾值,表示一個對象是否具有某個屬性

  • 注意: ( in ) 運算符 ( 不區分 ) 是 ( 自身屬性 ) 還是 ( 繼承屬性 )

for ...in 循環

自身屬性和繼承屬性都會被for...in循環遍歷

  • 為了只遍歷自身屬性,可以在for...in循環內部,用hasOwnProperty來過濾只有是自身屬性時,才遍歷
 const obj1 = {'name': 'wang'};
 const obj2 = {'age': 20};
 Object.setPrototypeOf(obj1, obj2);

 for(let x in obj1) {
     console.log(x, 'x') 
  }
 // name x
 // age x
 
 for(let y in obj1) {
    if( obj1.hasOwnProperty(y) ) {   ---- 參數屬性是否是實例對象的自身屬性
       console.log(y, 'y')
     }
 }
 // name y

對象的拷貝

如何要拷貝對象,需要有兩點相同

  1. 拷貝后的對象,要與原對象具有相同的 原型對象
  2. 考背后的對象,要與元對象具有相同的 實例屬性



















2018-7-1

語句

js程序的執行單位是 (行),一般情況下,每一行就是一個語句。

  • 語句是為了完成某種任務而進行的操作
  • 語句以分號結尾,一個分號就表示一個語句的結束
  • 多個語句可以寫在一行內
  • 分號前面可以沒有任何內容,js將其視為空語句
var a = 1 + 3 ; var b = 'abc';


解析:
1. 語句:語句以分號結尾;多個語句可以寫在一行內。
2. 表達式:為了得到返回值的計算式
3. 凡是預期為值的地方,都可以使用表達式
4. 賦值語句,等號右邊預期為值,所以可以使用各種表達式

表達式

表達式: 是一個為了得到返回值的計算式

語句和表達式的區別

  • 語句是為了完成某種任務而進行的操作,一般不需要返回值

  • 表達式是為了得到返回值,一定會返回一個值

  • js中,凡是預期為值的地方,都可以使用表達式(重要

變量

變量是對值的具名引用

  • 變量是對值的 具名引用
  • 變量就是為值取名 ,引用這個名字,就是引用這個值 ----------------------------- ( 重要 )
  • 變量的名字,就是變量名
  • 變量名區分大小寫
  • 如果只是聲明變量,而未賦值,則改變量的值是 undefined
  • 可以在同一條var命令中聲明多個變量
  • JavaScript 是一種動態類型語言,也就是說,變量的類型沒有限制,變量可以隨時更改類型

函數

js把函數看成一種值,凡是能使用值的地方,就能使用函數

  • 當有return語句時,返回return后面緊跟的表達式
  • 沒有return語句時,返回undefined
  • 函數只是一個可執行的值
  • (表達式是為了得到返回值的計算式,目的是為了得到返回值,預期為值的地方,都可以使用表達式)
  • 函數與其他數據類型地位相等,叫做第一等公民

函數名的提升

js將函數名,視同變量名。

  • 在用function命令聲明函數時,function后面的變量名存在變量提升
  • 注意:采用變量賦值時,如果先調用就會報錯
  • 因此,如果同時采用function命令和賦值語句聲明同一個函數,最后總是采用賦值語句的定義。
  • 不能在條件語句中聲明函數 ( if ) 和 ( try )
( 重要 )



總結: 
用變量賦值的方法聲明函數,和function聲明都存在變量提升
但是:變量賦值先調用會報錯,而function聲明不會報錯
( 先調用函數,function聲明不報錯,變量賦值聲明報錯 )




1. function命令聲明時的 ( 函數名提升 )
f();
function f() {}
不會報錯




2. 變量賦值聲明函數
f();
var f = function (){};
報錯TypeError: undefined is not a function,----------------  實際上相當于下面的代碼:
var f;
f();
f = function () {};

函數的name屬性

返回函數的名字

  • function聲明的函數,返回function后面的函數名
  • 變量賦值,匿名函數,返回變量名
  • 變量賦值,具名函數,返回function后面的函數名
  • name屬性的用處,就是獲得參數函數的名字
var myFunc = function () {};
function test(f) {
  console.log(f.name);
}
test(myFunc)    ----------------------- 返回 myFunc


上面代碼中,函數test內部通過name屬性,就可以知道傳入的參數是什么函數。

length屬性

函數的length返回函數定義時的參數個數

  • 返回 ( 定義時 ) 的 ( 參數個數 )
  • length方法提供了一種機制,判斷定義時和調用時參數的差異,以便實現面向對象編程的
    ( 方法重載 ) overload
const a = function(a,b,c) {
  return `${a},${b},${c}`
};
console.log( a.length ); -------------------------  length返回函數定義時的參數個數: 3
console.log( a(1,2) );    // 1,2,undefined

函數的toString方法

  • toString方法返回一個字符串,內容時函數的源碼 ( 包括函數內部的注釋代碼 )
    總結: name屬性,length屬性,toString()方法
const a = function(a,b,c) {
    return `${a},${b},${c}`
};
console.log( a.toString(), 'toString方法,返回函數的源碼字符串' )
console.log( a.length, 'length屬性,返回函數定義時的參數個數' );
console.log( a.name, 'name屬性返回函數的名字,function命令聲明,時后面的函數名,變量賦值,匿名時變量名,具名時function后的函數名')
console.log( a(1,2) );




結果:
function a(_a, b, c) {
                return _a + ',' + b + ',' + c;
            } toString方法,返回函數的源碼字符串
3 "length屬性,返回函數定義時的參數個數"
a name屬性返回函數的名字,function命令聲明,時后面的函數名,變量賦值,匿名時變量名,具名時function后的函數名
1,2,undefined

函數的作用域

( 作用域 ) scope指的是 ( 變量存在的范圍 )
es5有兩種作用域:( 全局作用域 ) 和 ( 函數作用域 )
es6新增一個作用域:( 塊級作用域 )

  • 全局作用域:在整個程序中存在,任何地方都可以讀取
  • 函數作用域:只能在函數內部讀取,函數外部無法讀取。( 用回調函數解決 )
  • 塊級作用域: 只在代碼塊內有效
  • 函數外部聲明的變量,是全局變量,可以在函數內部讀取 (global variable全局變量)
  • 函數內部定義的變量,函數外部無法讀取,稱為局部變量 (local variable局部便變量)
  • 函數內部定義的變量,會在該作用域內,覆蓋同名全局變量
  • 注意: 對于var命令,局部變量只能在函數內部聲明,在其他區塊聲明,一律都是全局變量

var a = 1;
var c = 3;

function x() {
    console.log(a, '函數內部,可以讀取函數外部的全局變量');
    var b = 2;
    var c = 4;
    console.log(c, '函數內部定義的變量,會在該作用域內覆蓋同名全局變量')
}
x();

if(true) {
    var y = 100;
}
console.log(y, '對于var命令來說,局部變量只能在函數中聲明,在其他區塊中聲明的變量都是全局變量')
console.log(b, '函數外部無法讀取函數內部的變量')

函數內部的變量提升

  • 和全局作用域一樣,函數作用域也會產生變量提升現象
  • var聲明的變量,不管在什么位置,變量聲明都會被提到函數頭部
function foo(x) {
  if (x > 100) {
    var tmp = x - 100;
  }
}
 -- 等同于
function foo(x) {
  var tmp;
  if (x > 100) {
    tmp = x - 100;
  };
}

函數本身的作用域

函數本身也是一個值,也有自己的作用域

  • 函數就是一個計算的值,凡是使用值的地方都可以使用函數
  • 表達式就是為了得到返回值的計算式,目的是為了得到返回值,凡是預期為值的地方,都可以使用表達式
  • 函數的作用域,是其聲明時所在的作用域,與其運行時的作用域無關
  • 函數的作用域,是函數聲明時的作用域,與函數運行時的作用域無關
  • 函數運行時的作用域,是函數聲明時的作用域,與函數調用時的作用域無關
  • 容易犯錯的點: 如果a函數調用b函數,卻沒有考慮到b函數不會引用a函數內部的變量 --------- ( 重要 )
var a = 1;
function x() {
    console.log(a, '函數的作用域,是函數聲明時的作用域,與函數調用時的作用域無關');
    console.log('容易犯錯的點: 如果a函數調用b函數,卻沒有考慮到b函數不會引用a函數的變量');
}
function y() {
    var a = 100;
    x();
}
y();  ---------------- 結果是1

總結:

  1. 函數外部聲明的函數,作用域綁定在函數外部
  2. 函數內部聲明的函數,作用域綁定在函數內部
( 重要 )



總結:
1. 函數外部聲明的函數,作用域綁定在函數外部
2. 函數內部聲明的函數,作用域綁定在函數內部
       第一個和第二點都滿足第三點
3. 函數的作用域,是函數聲明時的作用域,與函數調用時的作用域無關 !!!!!!!!


(1)
var a = 1;
var x = function () {
  console.log(a);
};
function y(f) {
  var a = 2;
  f();
}
y(x)   -------- 結果是1   ( 函數的作用域,是函數聲明時的作用域,與函數調用時的作用域無關 )



(2)
function foo() {
  var x = 1;
  function bar() {
    console.log(x);
  }
  return bar;
}
var x = 2;
var f = foo();
f()   -------- 結果是1   ( 函數的作用域,是函數聲明時的作用域,與函數調用時的作用域無關 )

參數

  • 函數沒辦法省略靠前的參數,而保留靠后的參數
  • 如果一點要省略靠前的參數,只有顯示的傳入 undefined
function f(a, b) {
  return a;
}

f( , 1) // SyntaxError: Unexpected token ,(…)   省略前面報錯
f(undefined, 1) // undefined   一定要省略前面,則只要傳入undefined

參數的傳遞方式

    1. 函數的參數如果是原始類型的值(數值,字符串,布爾值),傳遞方法是( 傳值傳遞 ),
      傳遞的是原始值的拷貝,在函數體內修改參數,不會影響到函數外部
    1. 函數的參數是復合類型的值(數組,對象,其他函數),傳遞方式是( 傳址傳遞 ),
      傳遞的是原始值的地址指針,在函數內部修改參數,會影響到原始值
    1. 注意:如果函數內部修改的不是參數對象的某個屬性,而是替換掉整個參數,這時不會影響到原始值
  • reference是引用的意思
  • ( 變量 ) 分為 ( 基本類型 ) 和 ( 引用類型 )
let str = 'abcdef';
let num = 12345;
let boo = true;  ------------------------------------ 原始類型的值

let arr = [1,2,3,4,5];
let obj = {'name': 'wang'}; ------------------------- 復合類型的值

let replaceArr = [1,2];
let replaceObj = {'name': 'zhang'} ------------------ 復合類型的值


function pass(num,str,boo, arr,obj,fun, replaceArr,replaceObj) {
    num = 100;
    str = 'oneHundred';
    boo =  false;     ---------- 參數是原始類型的值,傳值傳遞,修改參數,不影響原始值

    arr[0] = 100;
    obj.name = 'li';   --------- 參數是復合類型的值,傳址傳遞,修改參數,影響原始值

    replaceArr = [1,1,1];
    replaceObj = {'age': 30} --- 替換掉整個復合類型的參數,不會影響到原始值
}

pass(str, num, boo, arr, obj, replaceArr, replaceObj);
console.log(str, num, boo);
console.log(arr, obj);
console.log(replaceArr, replaceObj);

 結果:
 abcdef 12345 true
 [100, 2, 3, 4, 5] {name: "li"}
 [1, 2] {name: "zhang"}

同名參數

如果有同名參數,則取最后出現的那個值

function f(a, a) {
  console.log(a);
}
f(1, 2) // 2

-------------------------------------

function f(a, a) {
  console.log(a);
}
f(1) // undefined

arguments對象

arguments對象包含了函數 ( 運行時的所有參數 )

  • arguments[0] 是第一個參數,arguments[1] 是第二個參數,以此類推...
  • arguments對象只能在函數內部使用
  • 正常模式下,arguments對象可以在運行時修改
  • 嚴格模式下,arguments對象是一個只讀對象,修改無效,當不會報錯
  • arguments對象有一個callees屬性,返回它對應的原函數
function a(b,c,d,e) {
    console.log(arguments.length, 'arguments.length返回調用時傳入的參數個數,是調用時的參數個數');
    arguments[0] = 100;
    console.log(arguments[0], 'arguments對象,可以在運行時修改');
    console.log('在嚴格模式下,arguments對象是只讀,修改無效,當不會報錯');
}
a(1,2,3);
console.log(a.length, '函數的length屬性,返回函數定義時的參數個數');
console.log( arguments[1] ,'arguments對象只能在函數內部使用')
console.log(arguments.callee, 'arguments對象的callee屬性,返回它對應的原函數');



結果:
3 "arguments.length返回調用時傳入的參數個數,是調用時的參數個數"
100 "arguments對象,可以在運行時修改"
在嚴格模式下,arguments對象是只讀,修改無效,當不會報錯
4 "函數的length屬性,返回函數定義時的參數個數"
undefined "arguments對象只能在函數內部使用"
報錯: callee不能用于嚴格模式,react的類默認就是嚴格模式



注意:
1. 函數的length屬性,返回函數定義時的參數個數
2. arguments.length屬性,返回函數調用時傳入的參數個數
3. arguments對象只能在函數內部使用

閉包

閉包是定義在函數內部的函數

  • 函數的作用域是函數定義時的作用域,與調用時的作用域無關
  • 閉包的用處:
    1. 讀取函數內部的變量
    1. 將這些變量保存在內存中,即閉包可以記住它的誕生環境一直存在
    1. 封裝對象的私有屬性和私有方法
( 重要 )


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



解析:
1. 函數的作用域,是函數定義時的作用域,與函數調用時的作用域無關
2. f2聲明在f1的內部,f2調用時,f2的作用域是定義時候的作用域,即在f1的內部,所以是999,不是111
3. 閉包就是定義在一個函數內部的函數,注意: 一定是要在函數內部定義 !!!
( 閉包 )



function createIncrementor(start) {
  return function () {
    return start++;
  };
}
var inc = createIncrementor(5);
inc() // 5
inc() // 6
inc() // 7



解析:
1. 閉包inc使得函數createIncrementor的內部環境,一直存在。
2. inc始終在內存中,而inc的存在依賴于createIncrementor,因此也始終在內存中
   不會在調用結束后,被垃圾回收機制回收。
  • 閉包用于封裝對象的私有屬性和私有方法
閉包:用與封裝對象的私有屬性和私有方法


function Person(name) {
  var _age;
  function setAge(n) {
    _age = n;
  }
  function getAge() {
    return _age;
  }

  return {
    name: name,
    getAge: getAge,
    setAge: setAge
  };
}

var p1 = Person('張三');外層函數每次運行,都會生成一個新的閉包,閉包保存外層函數的內部變量,內存消耗大
p1.setAge(25);
p1.getAge() // 25

注意:
1. 外層函數每次運行,都會生成一個新的閉包,而這個閉包又會保留外層函數的內部變量,所以內存消耗很大。
2. 因此不能濫用閉包,否則會造成網頁的性能問題。

立即調用函數表達式

有時候,我們需要在定義函數之后,立即調用該函數。這時,你不能在函數的定義之后加上圓括號,這會產生語法錯誤。

  • 產生錯誤的原因是function關鍵字,可以作為語句,也可以作為表達式
  • 語句:用分號結尾,多個語句可以在一行。
  • 表達式:為了得到返回值的計算式,凡是預期為值的地方都可以使用表達式;
  • js規定,function關鍵字在行首,一律解釋成語句。因此,JavaScript引擎看到行首是function關鍵字之后,認為這一段都是函數的定義,不應該以圓括號結尾,所以就報錯了。
  • 解決方法就是不要讓function出現在行首,讓引擎將其理解成一個表達式。最簡單的處理,就是將其放在一個圓括號里面。
  • 總結:
    1. function出現在行首,是語句。語句不能以括號()結尾,必須以分號;結尾
    1. 用變量賦值方法聲明的函數,function...是一個表達式,直接加()就能立即執行調用
    1. 如果function在行首,又要立即調用,包一層括號即可
( 重要 )


語句
function f() {}            -------   函數的聲明語句


表達式
var f = function f() {}    --------  等號右邊預期是值,function是表達式,函數就是一個可執行的值
(function(){ ... }());         ------------- 語句不能以括號結尾,而是以分號結尾
// 或者
(function(){ ... })();

注意,上面兩種寫法最后的分號都是必須的。
如果省略分號,遇到連著兩個 IIFE,可能就會報錯。




--------------------------
注意: 如果是采用變量賦值的方式聲明函數,那么function本來就是表達式,所以可以直接加括號
const a = function() {
   console.log('11111111')
}();

eval命令

eval命令的作用是,將字符串當作語句執行

  • eval命令的作用是將字符串,當作語句執行
eval('var a = 1;');
a // 1
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 函數和對象 1、函數 1.1 函數概述 函數對于任何一門語言來說都是核心的概念。通過函數可以封裝任意多條語句,而且...
    道無虛閱讀 4,635評論 0 5
  • ??面向對象(Object-Oriented,OO)的語言有一個標志,那就是它們都有類的概念,而通過類可以創建任意...
    霜天曉閱讀 2,139評論 0 6
  • 第3章 基本概念 3.1 語法 3.2 關鍵字和保留字 3.3 變量 3.4 數據類型 5種簡單數據類型:Unde...
    RickCole閱讀 5,156評論 0 21
  • 文圖/ 資源整合(十六歲那年的詩) 既然鐘情于玫瑰 就勇敢地擁抱它 即使弄得 滿身是刺 也不必后悔 因為 你畢...
    培紅說情感閱讀 168評論 0 0
  • 忽然間好想你,人間天堂——美麗杭城;忽然間好想你,濃妝淡抹總相宜的西子湖:忽然間好想你,那些曾經在杭城奮斗打拼的日...
    秦悠然閱讀 274評論 4 1