js注意事项1(数据类型内存深浅拷贝闭包this指向callapplybind方法)

Posted 嘿起屁儿整

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了js注意事项1(数据类型内存深浅拷贝闭包this指向callapplybind方法)相关的知识,希望对你有一定的参考价值。

基本类型和引用类型

6大基本类型:number、string、boolean、null、undefined、symbol------在内存中占据空间小、大小固定。存在中,按值访问。
引用类型:对象、数组、函数 等(除了基本类型都是引用类型)-------在内存中空间大、大小不固定,因为数据量可能很多。存在中,按引用访问。

堆 栈内存

js中,内存分为 堆 、栈(ps:所有编程语言内存分配都差不多)
堆:存放大小不固定的数据,存放引用类型的具体数据
栈:存放大小固定的数据,存放值。所以可以将引用类型的地址值存在这里,但是引用类型的具体数据必须存在堆中。
入栈出栈:先进后出,后进先出(因为先放进去的在下面)
在这里插入图片描述

深拷贝和浅拷贝

浅拷贝:引用类型数据在复制后,改了引用类型数据,被拷贝方也会改变(原理:只拷贝第一层的数据到一个新对象上)
浅拷贝方法:
{ …xxx }
Object.assign({}, xxx)

var obja = { name: "张三" };
var objb = obja;
objb.name="李四";
console.log(obja); //李四
console.log(objb); //李四

深拷贝:引用数据类型在复制后,二者独立,不会因为改一个另一个也改变(原理:拷贝所有层数据到一个新对象上,完全独立互不影响)
深拷贝方法:
JSON.parse(JSON.stringtify( xxx ))

var obja = { name: "张三" };
var objb = JSON.parse(JSON.stringify(obja));
objb.name="李四";
console.log(obja); //张三
console.log(objb); //李四


var obja = { name: "张三" };
var objb = { ...obja };
objb.name = "李四";
console.log(obja); //张三
console.log(objb); //李四

var obja = { name: "张三" };
var objb = Object.assign({}, obja);
objb.name = "李四";
console.log(obja); //张三
console.log(objb); //李四

对象的浅拷贝与深拷贝

var obj = {a: 1, b: 2, c: { a: 3 },d: [4, 5]}
var obj1 = obj
var obj2 = JSON.parse(JSON.stringify(obj))//深拷贝常用方法
var obj3 = {...obj}
var obj4 = Object.assign({},obj)
obj.a = 999
obj.c.a = -999
obj.d[0] = 123
console.log(obj1) //{a: 999, b: 2, c: { a: -999 },d: [123, 5]}
console.log(obj2) //{a: 1, b: 2, c: { a: 3 },d: [4, 5]}
console.log(obj3) //{a: 1, b: 2, c: { a: -999 },d: [123, 5]}
console.log(obj4) //{a: 1, b: 2, c: { a: -999 },d: [123, 5]}

数组的浅拷贝与深拷贝

var arr = [1, 2, 3, [4, 5], {a: 6, b: 7}]
var arr1 = JSON.parse(JSON.stringify(arr))//深拷贝常用方法
var arr2 = arr
var arr3 = [...arr]
var arr4 = Object.assign([],arr)
console.log(arr === arr1) //false
console.log(arr === arr2) //true
console.log(arr === arr3) //false
console.log(arr === arr4) //false
arr[0]= 999
arr[3][0]= -999
arr[4].a = 123
console.log(arr1) //[1, 2, 3, [4, 5], {a: 6, b: 7}]
console.log(arr2) //[999, 2, 3, [-999, 5], {a: 123, b: 7}]
console.log(arr3) //[1, 2, 3, [-999, 5], {a: 123, b: 7}]
console.log(arr4) //[1, 2, 3, [-999, 5], {a: 123, b: 7}]

闭包

  • 将变量放在函数外,就是全局变量,都能访问,就不存在闭包一说。
  • 闭包:
  • 1、让外部访问函数内部的变量或者函数;
  • 2、避免全局变量过多,污染环境;
  • 3、局部变量常驻内存,会造成内存泄漏;
  • 4、每个闭包环境都是独立的,互不干扰
例子一:
function fn1(){
  var a = 10;  
  return function(){ 
        console.log(a);
  }
}
var b = fn1();
b();
 //10 因为返回一个函数,对于返回函数,a是全局变量

例子二:
function outerFn(){
  var i = 0; 
  function innerFn(){
      i++;
      console.log(i);
  }
  return innerFn;
}
var inner = outerFn();  
inner();
inner();
inner();
var inner2 = outerFn();
inner2();
inner2();
inner2(); 
//1 2 3 1 2 3  因为返回一个函数,对于函数,i是全局变量。闭包环境独立,互不影响,每次会在栈中开辟一个地址

例子三:
var i = 0;
function outerFn(){
  function innnerFn(){
       i++;
       console.log(i);
  }
  return innnerFn;
}
var inner1 = outerFn();
var inner2 = outerFn();
inner1();
inner2();
inner1();
inner2(); 
//1 2 3 4 因为i是全局变量

例子四:
function fn(){
	var a = 0;
	return function(){
		console.log(++a);                                     
	}
}
fn()(); 
fn()();
var a = fn();
a();
a();

//1 1,1 2 因为先返回函数,函数执行再返回a,所以有两个()() 。函数自执行属于闭包,互不影响。

例子五:

function outerFn(){
var i = 0;
  function innnerFn(){
      i++;
      console.log(i);
  }
  return innnerFn;
}
var inner1 = outerFn();
var inner2 = outerFn();
inner1();
inner2();
inner1();
inner2(); 
//1 1 2 2 因为闭包开辟的新地址,两个函数互不影响

例子六:
var  fn=(function(){
   var  i=10;
   function  fn(){
      console.log(++i);
   }
   return   fn;
})() 
fn(); 
fn();
//11 12 因为返回的是函数所以加了个()执行函数才返回值,然后调用两次此函数


例子七:
        function fun(a, b) {
            console.log(b);
            return {
                fun: function (c) {
                    return fun(c, a);
                }
            };
        }
        var a = fun(0);  //undefined 返回对象{fun:function....},此时,a为0,打印b没有
        a.fun(1);  //0  以0为基准,不管传入多少,其中的a已经定下来了
        a.fun(2);  //0  
        a.fun(3);  //0  
        var b = fun(0).fun(1).fun(2).fun(3);  //undefined 因为是对象形式,只能调用,没有这么多层
        var c = fun(0).fun(1);// 0 最开始b为undefined,然后调用方法传入1,0,此时a为1,b为0
        c.fun(2);//1 此时以1为基准,不管传入多少,其中a已经定了
        c.fun(3);//1

this指向

  1. this指向最终调用者,没有就是window
  2. 箭头函数 不会改变this指向,直接指向他的父级
  3. new 出来的函数对象,this指向函数
案例1var name = "外国人";
    function fn() {
      var name = "小小飞";
      console.log(this.name);
    }
    fn();
    //外国人,因为this指向最终调用者,window(同样window默认省略)
    
案例2
	var name = "外国人";
    var obj= {
      name: "小小飞",
      fn: function () {
        console.log(this.name); 
      }
    }
    obj.fn();
    //小小飞,因为this指向最终调用者,fn(同样window默认省略)

案例3
	var name = "外国人";
    var a = {
      name: "小小飞",
      fn: function () {
        console.log(this.name); 
      }
    }
    var f2= a.fn;
    f2();
    //外国人,因为this指向最终调用者window,然后f2是个函数

案例4
	var name = "外国人";
    var a = {
      name: "小小飞",
      fn:  ()=> {
        console.log(this.name); 
      }
    }
    a.fn();
    //外国人,因为箭头函数中没有this指向,再往上一层找。即a的上层window

案例5
	var name = "外国人";
    function fn() {
      var name = "小小飞";
      console.log(this.name,this);
    }
    new fn();
    //undefined fn(){} ,因为new的构造函数,this指向函数本身。然后函数没有name属性

call apply bind

  1. 这三个方法第一个参数都是绑定this指向,只是bind方法返回函数所以需要加上()执行一下
  2. 这三个方法其他参数都是传值,只是apply方法必须传数组,其他两个都是一一对应传就行
例子1var name = "小王", age = 18;
 		var obj = {
            name: "小张",
            objAge: console.log(this.age),
            myfun: function () {
                console.log(this.name + this.age)
            }
        }
        obj.objAge; //18
        obj.myfun();//小张undefined
		因为objAge中的this指向window(上层), 函数中的this指向obj对象(上层)

例子2var food ="苹果"
        function shows(){
            console.log(this.food)
        }
        shows(); //苹果
		因为函数中的this指向window(上层),food是全局变量所以随意访问

例子3var name = "小王", age = 18;
        var obj = {
            name: "小张",
            objAge: console.log(this.age),
            myfun: function () {
                console.log(this.name + this.age)
            }
        }
        var db = {
            name: "鲁班",
            age: "3"
        }
        obj.myfun.call(db)  //鲁班3
        obj.myfun.apply(db) //鲁班3
        obj.myfun.bind(db)() //鲁班3
		由此可见,call、applay、bind第一个参数都是绑定this指向,这里都指向db对象。只是bind返回函数,所以需要加个()执行函数

例子4var name = "小王", age = 18;
        var obj = {
            name: "小张",
            objAge: console.log(this.age),
            myfun: function (province, city) {
                console.log(this.name + this.age, "来自" + province + city)
            }
        }
        var db = {
            name: "鲁班",
            age: "3"
        }
        obj.myfun.call(db, "四川", "成都")
        obj.myfun.apply(db, ["四川", "成都"])
        obj.myfun.bind(db, "四川", "成都")()
        由此可见,call、applay、bind第一个参数,都是绑定this指向,其他参数都是传值。
        call和applay传值都是一一对应的,但是applay必须传数组格式

Finally , If there are some errors, please let me know

以上是关于js注意事项1(数据类型内存深浅拷贝闭包this指向callapplybind方法)的主要内容,如果未能解决你的问题,请参考以下文章

89.赋值重载以及深浅拷贝

堆与栈 | 对象深浅拷贝

深浅拷贝

js的深浅拷贝

前端面试题(js预编译this指向问题箭头函数中的thisjs深浅拷贝防抖函数节流函数)

Javascript 对象复制(深浅拷贝)