【フロントエンド面接】スコープとクローズド


1.テーマ
変数アップの理解を教えてください.
thisのいくつかの異なる使用シーンを説明します.
aラベルを10個作成し、クリックした時に該当する番号を弾きます.
スコープの理解方法
実際の開発におけるクローズドアプリケーション
call apply bindを手動で実現
2.知識点
2.1実行コンテキスト
範囲:セグメントスクリプトまたは関数
大域:変数定義、関数宣言script
関数:変数定義、関数宣言、this、argments(実行前)
関数宣言と関数式の違い:
a(); //                  。
var a = function(){}

b(); //          
function b(){}
変数の定義はデフォルトで彼の変数宣言をアップグレードします.
console.log(a);
var a = 0;
実際には
var a;
console.log(a);
a = 0;
2.2 this
thisは実行時に確認できますが、定義時には確認できません.
        var a = {
            name:'a',
            fn:function(){
                console.log(this.name);
            }
        }

        a.fn();  // a
        a.fn.apply({name:'b'});  // b  a.fn.call({name:'b'});
        var fn1 = a.fn();
        fn1();  // undefined
thisの使用シーン
コンストラクタ(構造のオブジェクトを指す)
    function Fun(name){
        this.name = name;
    }
    var f = new Fun('a');
    console.log(f.name);
オブジェクトの属性(オブジェクトを指す)
一般関数(windowを指す)
call apply bind
一つの関数を変えるためのthis指向です.使い方はちょっと違っています.
コール:後ろのパラメータは呼び出し関数のパラメータリストです.
function greet(name) {
  console.log(this.animal,name);
}

var obj = {
  animal: 'cats'
};

greet.call(obj,'  ');
apply:2番目のパラメータは呼び出し関数のパラメータ配列です.
function greet(name) {
  console.log(this.animal,name);
}

var obj = {
  animal: 'cats'
};

greet.apply(obj,['  ']);
bind:バインディング関数が呼び出されると、bindから入ってきたパラメータはターゲット関数のパラメータリストの開始位置に挿入され、バインディング関数に渡すパラメータはそれらの後に付いてきます.
var fun = function (name1,name2){
    console.log(this);
    console.log(name);
}.bind({a:1},"name1");
    fun("name2");
argmentsの中のthis:
var length = 10;
function fn(){
    alert(this.length)
}
var obj = {
    length: 5,
    method: function(fn) {
        arguments[0]()
    }
}
Obj.method(fn)/出力1ここでは5も出力していません.10も出力していません.逆に1も出力しています.面白いです.ここでargmentsはjavascriptの内蔵オブジェクト(mdn:argments-JavaScriptを参照することができます)で、一つのクラスの配列です.つまり、ここでargments[0]はあなたのmethod関数の最初のパラメータです.fnです.だから、argments[0]()の意味はfn()です.
ここで疑問がありますが、なぜここでは5が出力されていませんか?私はmethodの中でthisを使って、objを指すべきではありませんか?少なくとも10も出力します.これはどのような騒ぎですか?
実は、この1がargments.lengthであり、本関数のパラメータの個数です.なぜここのthisはargmentsを指していますか?Javascriptでは、配列は数字を属性名にするだけの方法です.つまり、argments[0]()の意味は、argments.0()の意味と同じです.
arguments = {
    0: fn, //    functon() {alert(this.length)} 
    1:      , //   
    2:      , //  
    ..., 
    length: 1 //      
}
だからここでalertが出てきた結果は1です.
出力するなら、5はどう書きますか?直接method:fnでいいです.
2.3スコープ
ブロックレベルのスコープがありません.
        if(true){
            var name = "test"
        }
        console.log(name);
ブロック内で変数を宣言しないようにします.
関数レベルのみのスコープ
2.4作用ドメインチェーン
自由変数の現在のスコープに定義されていない変数が自由変数です.
自由変数はその親レベルのスコープを探しに行きます.は、実行ではなく定義時の親レベルのスコープです.
        var a = 100;
        function f1(){
            var b = 200;
            function f2(){
                var c = 300;
                console.log(a); //    
                console.log(b); //    
                console.log(c);
            }
            f2();
        };
        f1();
2.5クローズド
一つの関数の中に他の関数を入れ子して、この関数をリセットして、このreturnから出てきた関数を変数に保存すると、閉じたパケットが作成されます.
クローズドの二つの使用シーン
1.関数を戻り値として使う
        function fun(){
            var a = 0;
            return function(){
                console.log(a); //    ,           
            }
        }

        var f1 = fun();
        a = 1000;
        f1();
2.関数をパラメータとして
        function fun(){
            var a = 0;
            return function(){
                console.log(a); //    ,           
            }
        }

        function fun2(f2){
            a = 10000
            f2();
        }

        var f1 = fun();

        fun2(f1);
具体的な説明は高級-閉包中の説明を見ます.
閉包の二つの役割:
他の関数の内部変数の関数を読み込むことができます.
関数内部の変数を常にメモリに保存することができます.
実際の応用シーン1:
クローズド・パケットは、グローバルに露出したくない変数のいくつかを「プライベート変数」としてカプセル化することができる.
積を計算する関数がある場合、mult関数はnumberタイプのパラメータをいくつか受信し、積結果を返します.関数の性能を向上させるために、キャッシュメカニズムを追加して、以前計算した結果をキャッシュして、次回同じパラメータに出会うと、直接に結果を返します.演算に参加する必要はありません.ここでは、キャッシュ結果を格納する変数は外部に露出する必要はなく、関数の運転が終了した後も保存する必要があるので、クローズドを採用することができます.
上のコード:
function calculate(param){
    var cache = {};
    return function(){
        if(!cache.parame){
            return cache.param;
        }else{
            //    ....
            //cache.param = result
            //       
        }
    }
}
実際の応用シーン2
局所変数の継続寿命
対象者はしばしばデータを報告するために用いられ、以下の通りである.
var report = function( src ){
    var img = new Image();
    img.src = src;
};
report( 'http://xxx.com/getUserInfo' );
しかし、いくつかの低バージョンのブラウザの実装によってバグが存在し、これらのブラウザでレポート関数を使ってデータをアップロードすると30%ぐらいのデータが失われるということは、レポート関数は毎回HTTP要求を成功させることではないということです.
データの損失の原因は、レポート関数における局所変数であり、レポート関数の呼び出しが終了すると、すぐに一部変数が破壊され、HTTP要求がまだ発行されていないかもしれないので、今回の要求は失われます.
今は変数をクローズドで閉じておけば、失われた問題を解決できます.
var report = (function(){
    var imgs = [];
    return function( src ){
        var img = new Image();
        imgs.push( img );
        img.src = src;
    }
})();
短所を包含する:資源を浪費する!
3.問題の解答
3.1変数アップの理解を話してください.
変数の定義と関数宣言
関数宣言と関数表現の違いに注意してください.
変数の定義はデフォルトで彼の変数宣言をアップグレードします.
console.log(a);
var a = 0;
実際には
var a;
console.log(a);
a = 0;
3.2 thisのいくつかの異なる使用シーンを説明する
  • 構造関数(指向構造のオブジェクト)
  • オブジェクト属性における
  • 一般関数(windowを指す)
  • call apply bind
  • 3.3 aラベルを10個作成し、クリックした時に該当する番号を弾きます.
    実現方法1:letでiを宣言する.
            var body = document.body;
            console.log(body);
            for (let i = 0; i < 10; i++) {
                let obj = document.createElement('i');
                obj.innerHTML = i + '
    '; body.appendChild(obj); obj.addEventListener('click',function(){ alert(i); }) }
    実現方法2包装作用エリア
        var body = document.body;
        console.log(body);
        for (var i = 0; i < 10; i++) {
            (function (i) {
                var obj = document.createElement('i');
                obj.innerHTML = i + '
    '; body.appendChild(obj); obj.addEventListener('click', function () { alert(i); }) })(i) }
    3.4実際の開発におけるクローズドの応用
    他の関数の内部変数の関数を読み込むことができます.
    関数内部の変数を常にメモリに保存することができます.
    パッケージ変数
    アプリケーション1
    var report = (function(){
        var imgs = [];
        return function( src ){
            var img = new Image();
            imgs.push( img );
            img.src = src;
        }
    })();
    変数の破壊を防ぐために使用します.
    アプリケーション2
        function isFirstLoad() {
            var arr = [];
            return function (str) {
                if (arr.indexOf(str) >= 0) {
                    console.log(false);
                } else {
                    arr.push(str);
                    console.log(true);
                }
            }
        }
    
        var fun = isFirstLoad();
        fun(10);
        fun(10);
    arrを関数の内部にカプセル化し、勝手に変更することを禁止し、変数の破壊を防止します.
    3.5手動でcall apply bindを実現する
  • contextはオプションのパラメータです.伝えないなら、デフォルトのコンテキストはwindowです.
  • contextはSymbol属性を作成し、呼び出し後に削除します.context
  • に影響しません.
        Function.prototype.myCall = function (context) {
          if (typeof this !== 'function') {
            return undefined; //      Function.prototype.myCall()     
          }
          context = context || window;
          const fn = Symbol();
          context[fn] = this;
          const args = [...arguments].slice(1);
          const result = context[fn](...args);
          delete context[fn];
          return result;
        }
    appryは類似のcallを実現して、パラメーターは配列です.
        Function.prototype.myApply = function (context) {
          if (typeof this !== 'function') {
            return undefined; //      Function.prototype.myCall()     
          }
          context = context || window;
          const fn = Symbol();
          context[fn] = this;
          let result;
          if (arguments[1] instanceof Array) {
            result = context[fn](...arguments[1]);
          } else {
            result = context[fn]();
          }
          delete context[fn];
          return result;
        }
    1.コンストラクタコールかどうかを判断する
    2.パラメータはターゲット関数の開始位置に挿入します.
        Function.prototype.myBind = function (context) {
          if (typeof this !== 'function') {
            throw new TypeError('Error')
          }
          const _this = this
          const args = [...arguments].slice(1)
          return function F() {
            //           
            if (this instanceof F) {
              return new _this(...args, ...arguments)
            }
            return _this.apply(context, args.concat(...arguments))
          }
        }