关于js函数的一些剖析 如bind call apply 原型链 作用域 作用域链 闭包的 函数提升的理解

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title></title>
    </head>
    <body>
        <script type="text/javascript">
            // 函数作用域的例子理解
            console.log(aa(1)) // 1
            function aa(n) {
                return n * n
            }
            // console.log(bb) // Cannot access 'bb' before initialization
            // console.log(bb(2)); // Cannot access 'bb' before initialization
            var bb = function(n) {
                return n * n;
            }
            console.log(bb(2)) // 4
            // 2.函数提升仅适用于函数声明,而不适用于函数表达式 由上面的例子得出结论
            
            // 3.函数表达式也可以提供函数名,并且可以用于在函数内部代指其本身
            var cc = function fn(n) {
                return n<2 ? 1 : n*fn(n-1)
            }
            console.log(cc(3)) // 6 fn(3) => 3*fn(2) => 3*2*fn(1) => 3*2*1 = 6
            
            // 嵌套的函数
            function getNum() {
              var num1 = 6,
                  num2 = 8;
              function add() {
                return num1 + num2
              }
              return add();
            }
            // console.log(num1,num2) // num1 num2 is not defined =>得出 在函数内定义的变量不能在函数之外的任何地方访问
            console.log(getNum()) // 14 =>得出 在另一个函数中定义的函数也可以访问在其父函数中定义的 所有变量和父函数有权访问的任何其他变量
            
            var a = 2 , b = 4;
            function multiply() {
              return a * b;
            }
            console.log(multiply()) // 8 => 得出 定义在全局域中的函数可以访问所有定义在全局域中的变量
            
            // 调用自身的函数我们称之为递归函数
            function nodeTree(node) {
              if (node == null){
                 return;
              }
              // do something with node
              for (var i = 0; i < node.childNodes.length; i++) {
                nodeTree(node.childNodes[i]);
              }
            }
            // 箭头函数  箭头函数不会创建自己的this,它只会从自己的作用域链的上一层继承this
            // 箭头函数不能用作构造器,和 new一起用会抛出错误
            // 箭头函数没有prototype属性
            // yield 关键字通常不能在箭头函数中使用(除非是嵌套在允许使用的函数内)
            var elements = [
              'ssssss',
              'ssssss',
              'ssssss',
              'ssssss'
            ];
            // 当箭头函数只有一个参数时,可以省略参数的圆括号
            const ele = elements.map(e => { 
              return e.length; 
            }); 
            // 当箭头函数的函数体只有一个 `return` 语句时,可以省略 `return` 关键字和方法体的花括号
            const eles = elements.map(e => e.length); 
            console.log(ele,eles) // 返回数组 [6, 6, 6, 6] [6, 6, 6, 6]
            
            // call apply 调用函数的区别以及bind的作用:1.参数较少用call,参数较多用apply 2.传参方式不一样 call正常传  apply传数组
            function fn(a,b){
                console.log(this) 
                console.log(a+b)
            }
            //call调用函数,第一个参数用来改变this指向,除了第一个参数外的参数作为实参
            fn.call({name:'test-call'},10,20) // {name: "test-call"} 30
            // apply第二个参数为实参列表,调用会平铺开作为参数 apply传的是数组
            fn.apply({name:'test-apply'},[10,20]) // {name: "test-apply"} 30
            //作用:创建并返回一个新的函数,新函数跟fn函数长得一模一样,新函数的this指向被固定成了参数thisArg,并不会主动调用函数
            var newFn=fn.bind(this)
            console.log(newFn) // 返回 fn 场景,改变定时器中的this 重新绑定上下文 类似 var _this/that = this;这个操作
            
            // 构造函数的调用 这里扯到函数的调用分为 call、apply 、构造函数的调用以及函数普通的调用 constr()
            function constr(){
                console.log(this,'111111') // constr {}  "111111"
                this.a = 1
            }; 
            var so = new constr();
            console.log(so) // constr {a: 1}
            
            // 函数的getter和setter
            var newConstr = {
                _name: '',
                get name() { 
                    return this._name 
                },
                set name(newName) { 
                    this._name = newName 
                }
            }
            // 测试 get set基本用法
            console.log(newConstr.name) // 输出 --> ''
            newConstr.name = 'set了一个值';
            console.log(newConstr.name) // 输出 --> set了一个值
            
            // Object.defineProperty的使用
            var testConstr = function() {
                var _name = '';
                var obj = {};
                Object.defineProperty(obj, 'name', {
                    configurable: true,
                    enumerable: true,
                    get: function() {
                        return _name;
                    },
                    set: function(newName) {
                        _name = newName;
                    }
                })
                return obj;
            }();
            testConstr.name = "使用Object.defineProperty方法set了一个值";
            console.log(testConstr.name) // 输出 --> 使用Object.defineProperty方法set了一个值
            
            // 原型链
            // 每个实例对象( object )都有一个私有属性(称之为 __proto__ )指向它的构造函数的原型对象(prototype )。
            // 该原型对象也有一个自己的原型对象( __proto__ ) ,层层向上直到一个对象的原型对象为 null。
            // 根据定义,null 没有原型,并作为这个原型链中的最后一个环节。
            let fun = function () {
               console.log(this) // fun(){}
               this.a = 1;
               this.b = 2;
            }
            /* 这么写也一样
            function fun() {
              this.a = 1;
              this.b = 2;
            }
            */
            let ob = new fun();
            console.log(ob)  // {a: 1, b: 2}
            // 在fun函数的原型上定义属性
            // 不要在 fun 函数的原型上直接定义 fun.prototype = {b:3,c:4};这样会直接打破原型链
            fun.prototype.b = 3;
            fun.prototype.c = 4;
            console.log(ob.a,ob.b,ob.c,ob.d); // 1 2 4 undefined
            // 总结 (给对象设置属性会创建自有属性。获取和设置属性的唯一限制是内置 getter 或 setter 的属性)
            // 整个原型链如下: {a:1, b:2} ---> {b:3, c:4} ---> Object.prototype---> null
            // a是ob的自身属性值为 1
            // b是ob的自身属性值为 2 原型上也有一个'b'属性,但是它不会被访问到,这种情况被称为"属性遮蔽"
            // c不是ob的自身属性吗 ==> 那看看它的原型上有没有 c是ob.[[Prototype]]的属性值为 4
            // d不是ob的自身属性 ==> 那看看它的原型上有没有 d不是 ob.[[Prototype]] 的属性 ==> 那看看它的原型上有没有 ob.[[Prototype]].[[Prototype]] 为 null,停止搜索 找不到d属性,返回undefined
            
            // 当继承的函数被调用时,this 指向的是当前继承的对象,而不是继承的函数所在的原型对象
            var foo = {
              a: 2,
              fb: function(){
                return this.a + 1;
              }
            };
            console.log(foo.fb()); // 3
            // 当调用 foo.fb 时,'this' 指向了 foo.
            var op = Object.create(foo); // op是一个继承自 foo 的对象
            console.log(op,'op') // {} 'op'
            op.a = 4; // 创建 op 的自身属性 'a'
            console.log(op,'op') // {a: 4}
            console.log(op.fb()); // 5
            // 调用 op.fb 时,'this' 指向了 op 又因为 op 继承了 foo 的 fb 函数 所以,此时的 'this.a' 即 op.a,就是 op 的自身属性 'a' 
            
            // 嵌套函数和闭包 
            // 内部函数只可以在外部函数中访问。内部函数包含外部函数的作用域
            // 内部函数形成了一个闭包:它可以访问外部函数的参数和变量,但是外部函数却不能使用它的参数和变量。
            function outfun(a) {
              function infun(b) {
                return a + b;
              }
              return infun;
            }
            let fnIn = outfun(3); // 可以这样想:给一个函数,使它的值加3
            res = fnIn(5);
            console.log(res,'res') // 8 "res"
            res1 = outfun(3)(5); 
            console.log(res1,'res1') // 8 "res1"
            
            // 多层嵌套函数,B和C都形成了闭包,所以B可以访问A,C可以访问B和A;闭包可以包含多个作用域;他们递归式的包含了所有包含它的函数作用域。这个称之为作用域链
            function A(a) {
              function B(b) {
                function C(c) {
                  console.log(a + b + c); // 输出6
                }
                C(3);
              }
              B(2);
            }
            A(1);
            
        </script>
    </body>
</html>

 

posted @ 2020-06-08 09:42  鱼樱前端  阅读(212)  评论(0编辑  收藏  举报