JS 函数与作用域 引用类型对象拷贝

函数声明和函数表达式有什么区别

http://www.jianshu.com/p/888fded98527
  • JavaScript 中需要创建函数的话,有两种方法:函数声明、函数表达式,各自写法如下:

    // 方法一:函数声明
    function foo() {}
    
    // 方法二:函数表达式
    var foo = function () {};
  • 另外还有一种自执行函数表达式,主要用于创建一个新的作用域,在此作用域内声明的变量不会和其它作用域内的变量冲突或混淆,大多是以匿名函数方式存在,且立即自动执行:

    (function () {
      // var x = ...
    })();

    此种自执行函数表达式归类于以上两种方法的第二种,也算是函数表达式。

    方法一和方法二都创建了一个函数,且命名为 foo,但是二者还是有区别的。JavaScript 解释器中存在一种变量声明被 提升(hoisting) 的机制,也就 是说变量(函数)的声明会被提升到作用域的最前面,即使写代码的时候是写在最后面,也还是会被 提升 至最前面。

    例如以下代码段:

    alert(foo); // function foo() {}
    alert(bar); // undefined
    function foo() {}
    var bar = function bar_fn() {};
    alert(foo); // function foo() {}
    alert(bar); // function bar_fn() {}

    输出结果分别是function foo() {}undefinedfunction foo() {}function bar_fn() {}

    可以看到 foo 的声明是写在 alert 之后,仍然可以被正确调用,因为 JavaScript 解释器会将其提升到 alert 前面,而以函数表达式创建的函数 bar则不享受此待遇。

    那么bar 究竟有没有被提升呢,其实用 var声明的变量都会被提升,只不过是被先赋值为 undefined 罢了,所以第二个 alert 弹出了 undefined

    所以,JavaScript 引擎执行以上代码的顺序可能是这样的:
    1. 创建变量 foo 和 bar,并将它们都赋值为 undefined
    2. 创建函数 foo 的函数体,并将其赋值给变量 foo
    3. 执行前面的两个 alert
    4. 创建函数 bar_fn,并将其赋值给 bar
    5. 执行后面的两个 alert
注:

严格地说,再 JavaScript 中创建函数的话,还有另外一种方法,称为“函数构造法”:

var foo = Function('alert("hi!");');
var foo = new Function('alert("hi!");'); // 等同于上面一行

new function是可以传参数的。
比如

var sum= new  Function("a", "b", "c", "return a+b+c");
sum(1,2,3)//6

什么是变量的声明前置?什么是函数的声明前置

先来看一个例子

fn1();   // 输出:我是函数声明
fn2();   //  报错
console.log(a); // 输出:undefined

function fn1() {
    console.log( "我是函数声明" );
}

var fn2 = function() {
    console.log( "我是函数表达式" );
}

var a = 20

因为JS对使函数声明前置,所以fn1()在函数声明前执行仍然可以得到正确答案,而函数表达式fn2则报错,
为什么?我们先要弄清楚:

JS解释器如何找到我们定义的函数和变量?
通过 变量对象(Variable Object, VO)来获取。VO是一个抽象概念的“对象”,它用于存储执行上下文中的:1. 变量2. 声明3. 函数参数

函数的VO分为两个阶段——变量初始化代码执行。在变量初始化阶段,VO按照如下顺序填充:
1. 函数参数(若未传入,初始化该参数值为undefined)
2. 函数声明(若发生命名冲突,会覆盖)
3. 变量声明(初始化变量值为undefined,若发生命名冲突,则忽略)

注意:函数表达式与变量初始化无关。

在变量初始化阶段,需要先对arguments变量进行初始化(激活对象AO),再把函数体内的变量声明与函数声明存储在AO内,VO(functionContext) === AO

根据 VO数据 填充顺序看以下例子

var x = 10;
bar();

function foo() {
    console.log(x);
}

function bar() {
   var x = 30;
    foo();  //得到什么? 
}

/*
1. globalContext = {
    AO: {
          x: 10
          foo: function
          bar: function 
    }
    Scope: null
}

// 声明 foo 时 得到下面
foo.[ [scope] ] = globalContext.AO
// 声明 bar 时 得到下面
bar.[ [scope] ] = globalContext.AO

// 当调用bar(),进入bar 的执行上下文

2. barContext = {
    AO: {
         x: 30
    }
    Scope = bar.[ [scope] ]    // globalContext.AO
}

//当调用 foo() 时,先从 bar 执行上下文中的 AO里找,找不到再从 bar 的 [[scope]]里,找到后即调用

3. fooContext =  {
    AO: {

    }
    Scope = foo.[ [scope] ]  // globalContext.AO
}

*/

所以console.log(x) 是 10

arguments 是什么

  • arguments 是一个类似数组的对象, 对应于传递给函数的参数。
  • arguments.length表示的是实际上向函数传入了多少个参数。
  • arguments对象是所有函数中可用的局部变量。你可以使用arguments对象在函数中引用函数的参数。此对象包含传递给函数的每个参数的条目,第一个条目的索引从0开始。例如,如果一个函数传递了三个参数,你可以参考它们如下:

arguments[0]
arguments[1]
arguments[2]

  • 在函数内部,你可以使用arguments对象获取到该函数的所有传入参数

Paste_Image.png

函数的"重载"怎样实现

首先想声明下,什么是函数重载,javascript中不存在函数重载的概念,(其实是个伪命题)但一个函数通过不同参数列表(arguments)来实现各个功能,我们都叫函数重载,这就是牛逼闪闪的 JavaScript 函数重载

/*
    * 传统方法一
    * */
    var overrideMethod = function () {
        switch (arguments.length) {
            case 0 :
                console.log("class no body");
                break;
            case 1 :
                console.log("class has one student");
                break;
            default :
                console.log("class has more students");
        }
    }
    overrideMethod("test","blue"); //class has more students
    overrideMethod("test-blue"); //class has one student
    overrideMethod(); //class no body
    /*
    * 我们希望对象Company拥有一个find方法,当不传任何参数时,
    * 就会把Company.names里面的所有元素返回来;
    * 因为find方法是根据参数的个数不同而执行不同的操作的,
    * 所以,需要有一个overrideCompanyFind方法,能够如下的为Company添加find的重载:
    * */
    var company = {
        names : ["baron" , "Andy" ,"Lily" , "Blures"],
        find : function () {
            return this.names.length
        }
    };
    var overrideCompanyFind = function (object , method , cb) {
        var oldMethod = object[method];
        //给object 重新赋予新的方法
        object[method] = function () {
            if (cb.length == arguments.length) {
               return cb.apply(this,arguments)
            }else if(typeof oldMethod== 'function'){
               return oldMethod.apply(this,arguments)
            }
        };
    };
    overrideCompanyFind(company,'find',function (name , name2) {
        return this.names
    });
    overrideCompanyFind(company,'find',function (name) {
        return name + '的位置是' + this.names.indexOf(name) + '排'
    });
    console.log(company.find()); //4
    console.log(company.find('Lily')); // Lily的位置是2排
    console.log(company.find('Lily','baron')); //["baron", "Andy", "Lily", "Blures"]

立即执行函数表达式是什么?有什么作用

立即调用函数表达式可以令其函数中声明的变量绕过JavaScript的变量置顶声明规则,还可以避免新的变量被解释成全域变量或函数名占用全域变量名的情况。与此同时它能在禁止访问函数内声明变量的情况下允许外部对函数的调用。有时,这种编程方法也被叫做“自执行(匿名)函数”,但“立即调用函数表达式”是语义上最准确的术语。

// 下面2个括弧()都会立即执行

(function () { /* code */ } ()); // 推荐使用这个
(function () { /* code */ })(); // 但是这个也是可以用的

// 由于括弧()和JS的&&,异或,逗号等操作符是在函数表达式和函数声明上消除歧义的
// 所以一旦解析器知道其中一个已经是表达式了,其它的也都默认为表达式了
// 不过,请注意下一章节的内容解释

var i = function () { return 10; } ();
true && function () { /* code */ } ();
0, function () { /* code */ } ();

// 如果你不在意返回值,或者不怕难以阅读
// 你甚至可以在function前面加一元操作符号

!function () { /* code */ } ();
~function () { /* code */ } ();
-function () { /* code */ } ();
+function () { /* code */ } ();

// 还有一个情况,使用new关键字,也可以用,但我不确定它的效率
// http://twitter.com/kuvos/status/18209252090847232

new function () { /* code */ }
new function () { /* code */ } () // 如果需要传递参数,只需要加上括弧()
有什么用?

javascript中没用私有作用域的概念,如果在多人开发的项目上,你在全局或局部作用域中声明了一些变量,可能会被其他人不小心用同名的变量给覆盖掉,根据javascript函数作用域链的特性,可以使用这种技术可以模仿一个私有作用域,用匿名函数作为一个“容器”,“容器”内部可以访问外部的变量,而外部环境不能访问“容器”内部的变量,所以( function(){…} )()内部定义的变量不会和外部的变量发生冲突,俗称“匿名包裹器”或“命名空间”。


求n!,用递归来实现

function fn(n){
    if(n===1){
         return 1;
    }
   alert( n * fn(n-1));
}
fn(5);

以下代码输出什么?

function getInfo(name, age, sex){
        console.log('name:',name);
        console.log('age:', age);
        console.log('sex:', sex);
        console.log(arguments);
        arguments[0] = 'valley';
        console.log('name', name);
    }

getInfo('饥人谷', 2, '男');  //  name: 饥人谷    age:2    sex:男    ["饥人谷", 2, "男"]    name valley
getInfo('小谷', 3);   // name: 小谷    age:3    sex:undefined    ["小谷", 3]    name valley
getInfo('男');   // name: 男    age:undefined    sex:undefined    ["男"]    name valley

写一个函数,返回参数的平方和?

   function sumOfSquares(){
        var sum = 0;
       for (var i=0; i<arguments.length; i++) {
              sum +=  arguments[i] * arguments[i];
       }
       console.log(sum);
   }
   var result = sumOfSquares(2,3,4)
   var result2 = sumOfSquares(1,3)
   console.log(result)  //29
   console.log(result)  //10

如下代码的输出?为什么

    console.log(a);    //  undefined
    var a = 1;       
    console.log(b);     // 报错

JS变量声明会被 提升 所以console.log(a) var a 会提前,但是还没被赋值,所以输出undefined
console.log(b) 因为变量b没有被声明,所以报错

如下代码的输出?为什么

    sayName('world');     // hello world
    sayAge(10);          // 报错
    function sayName(name){
        console.log('hello ', name);
    }
    var sayAge = function(age){
        console.log(age);
    };

sayName('world');根据函数声明会被提升,所以输出hello world。sayAge(10),报错是因为var sayAge = function(age){}是一个函数表达式,声明var sayAge时,还不是一个函数,sayAge(10) 调用在声明前,所以报错。

如下代码输出什么? 写出作用域链查找过程伪代码

var x = 10
bar() 
function foo() {
  console.log(x)
}
function bar(){
  var x = 30
  foo()
}           //  输出 10

1. globalContext = {
    AO: {
       x: 10
       foo: funciton
       bar: funciton
    }
    Scope: null
}

//声明 foo() 时 得到
foo.[ [scope] ] = globalContext.AO
//声明 bar() 时 得到
bar.[ [scope] ] = globalContext.AO
// 当调用bar(),进入bar 的执行上下文
2. barContext = {
    AO: {
      x: 30
    }
   Scope: bar.[ [scope] ]
}
//当调用 foo() 时,先从 bar 执行上下文中的 AO里找,找不到再从 bar 的 [[scope]]里,找到后即调用
  fooContext = {
    AO: {

    } 
   Scope: foo.[ [scope] ]
}

所以console.log(x) 得到10

如下代码输出什么? 写出作用域链查找过程伪代码

var x = 10;
bar() 
function bar(){
  var x = 30;
  function foo(){
    console.log(x) 
  }
  foo();
}

以下代码输出什么? 写出作用域链的查找过程伪代码

var x = 10;
bar() 
function bar(){
  var x = 30;
  (function (){
    console.log(x)
  })()
}

1. globalContext = {
   AO: {
      x: 10
      bar: funciton
   }
   Scope: null
}

bar.[ [scope] ] = globalContext.AO

2. barContext = {
    AO: {
      x: 30
    }
   Scope: bar.[ [scope] ]
 }

声明x,x赋值10 > 执行bar() > 声明x,x赋值30 > 立即执行匿名函数 > console.log(x) > 到bar上下文AO中找到x = 30 ,所以console.log(x) 得到30

//

以下代码输出什么? 写出作用域链查找过程伪代码

var a = 1;

function fn(){
  console.log(a)
  var a = 5
  console.log(a)
  a++
  var a
  fn3()
  fn2()
  console.log(a)

  function fn2(){
    console.log(a)
    a = 20
  }
}

function fn3(){
  console.log(a)
  a = 200
}

fn()
console.log(a)

1. globalContext = {
    AO: {
       a: 1
       fn: funciton
       fn3: funciton
    }
   Scope:null
}
// fn.[ [scope] ] = globalContext.AO   
// fn3.[ [scope] ] = globalContext.AO

2. fnContext = {
    AO: {
       a: undefine
       fn3: funciton
       fn2: funciton
    }
   Scope: fn.[ [scope] ]    // fn.[ [scope] ] = globalContext,AO
}
}

fn2Context {
     AO: {

     } 
    Scope: fn2.[ [scope] ]    // fn2.[ [scope] ] = fnContext,AO
}

fn3Context {
     AO: {
     }
    Scope: fn3.[ [scope] ]    // fn3.[ [scope] ] = globalContext,AO
}

执行fn() > console.log(a) 此时 a = undefined;  > console.log(a) 此时 a = 5 > a++ 此时 a = 6 > 执行fn3() , console.log(a) 找到globalContext.AO里 a = 1;  a = 200 此时globalContext.AO里 a = 200   >  执行fn2() , console.log(a) 找到fnContext.AO里 a = 6, 然后a = 20 此时 fnContext.AO里 a = 20;
> 执行 console.log(a) 此时 a = 20    >   最后 执行 console.log(a)  得到 a = 200

结果  undefined > 5 > 1 > 6 > 20 > 200

1. 引用类型有哪些?非引用类型有哪些

  • 基本类型值(数值、布尔值、null和undefined):指的是保存在栈内存中的简单数据段;http://www.jianshu.com/p/4df8e8c02641
  • 引用类型值(对象、数组、函数、正则):指的是那些保存在堆内存中的对象,变量中保存的实际上只是一个指针,这个指针执行内存中的另一个位置,由该位置保存对象
  • string类型有些特殊,因为字符串具有可变的大小,所以显然它不能被直接存储在具有固定大小的变量中。由于效率的原因,我们希望JS只复制对字符串的引用,而不是字符串的内容。但是另一方面,字符串在许多方面都和基本类型的表现相似,而字符串是不可变的这一事实(即没法改变一个字符串值的内容),因此可以将字符串看成行为与基本类型相似的不可变引用类型。

2. 如下代码输出什么?为什么

var obj1 = {a:1, b:2};
var obj2 = {a:1, b:2};
console.log(obj1 == obj2);   // false   , 因为两个不同的对象储存的指针不一样。
console.log(obj1 = obj2);    // object { a: 1, b: 2 }   ,  意思是把obj2的指针给obj1, 然后打印出对象obj1
console.log(obj1 == obj2);  // true    ,  因为obj1和obj2的指针相同,所以指针都指向同一个内存空间,所以两个对象相等。

3. 如下代码输出什么? 为什么

var a = 1
var b = 2
var c = { name: '饥人谷', age: 2 }
var d = [a, b, c]

var aa = a
var bb = b
var cc = c
var dd = d

a = 11
b = 22
c.name = 'hello'
d[2]['age'] = 3

console.log(aa)    // 1 , aa = a ,所以aa的值为1,虽然a = 11 改变了a的值,但是对aa没有影响。
console.log(bb)   //  2   , 同理如上
console.log(cc)   //  Object { name: "hello", age: 3 }   ,因为cc = c 是对象c 把指针给了cc ,所以cc 和c的指针相同,都指向同一个内存空间,c.name = "hello" 也会影响到cc.name
console.log(dd)  // [ 1, 2, Object { age: 3, name: "hello" } ]  同理如上,dd和d指针相同,指向同一个内存空间,d[2]['age'] = 3,也影响到对象dd的内容

4. 如下代码输出什么? 为什么

var a = 1
var c = { name: 'jirengu', age: 2 }

function f1(n){
  ++n
}
function f2(obj){
  ++obj.age
}

f1(a) 
f2(c) 
f1(c.age) 
console.log(a)  // 1 ,  首先执行函数f1(a) 结果是++n得到2,但是a还是等于1,所以console.log(a) 得到1
console.log(c) //   Object { name: "jirengu", age: 3 }  , 执行函数f2(c) ,其中obj = c,c的指针给了obj,两个对象指向同一个内存空间,
                        //   然后++obj.age, 得到age:3  ,再执行函数f1(c.age) ,其中只是把c.age的值取出来赋给了n ,++n得到4,但是不影响对象c。
                       // 所以console.log(c) 得到Object { name: "jirengu", age: 3 }

5. 过滤如下数组,只保留正数,直接在原数组上操作

var arr = [3,1,0,-1,-3,2,-5];
function filter(arr){
     for (var i=0; i<arr.length; i++){
           console.log(arr[i]);
           if(arr[i] <= 0){
                   arr.splice(i--,1);
            }
     }
      return arr;
}
filter(arr)
console.log(arr) // [3,1,2]

6. 过滤如下数组,只保留正数,原数组不变,生成新数组

var arr = [3,1,0,-1,-3,2,-5]
function filter(arr){
     var newArr = [];
     for(var i=0, j=0; i<arr.length; i++){
           if(arr[i] > 0) {
                 newArr[j] = arr[i];
                 j++;
           }
     }
  return newArr;
}
var arr2 = filter(arr)
console.log(arr2) // [3,1,2]
console.log(arr)  // [3,1,0,-1,-2,2,-5]

7. 写一个深拷贝函数,用两种方式实现

//第一种
function clone(obj){
    var newObject={};
for (var key in obj) {
    if(typeof obj[key] === 'number'||typeof obj[key] === 'boolean'||typeof obj[key] === 'string'||obj[key] ===undefined||obj[key] === null){
      newObject[key]=obj[key];
} else{ 
    newObject[key]=clone(obj[key])
  }
}
return newObject;
}


//第二种
var cloneObj = JSON.parse(JSON.stringify(obj));

//JSON.parse() 方法用于将一个 JSON 字符串转换为对象。
//JSON.stringify() 方法将 JavaScript 对象转换为字符串。

JSON.parse() 不允许用逗号作为结尾

// both will throw a SyntaxError
JSON.parse("[1, 2, 3, 4, ]");
JSON.parse('{"foo" : 1, }');

JSON在ie中从ie8开始兼容,其他类型浏览器则几乎全部兼容,JSON api使用的门槛也变低了,多数浏览器可以直接
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值