ホームページ  >  記事  >  ウェブフロントエンド  >  jsでの受け渡しとコピーについて詳しく解説

jsでの受け渡しとコピーについて詳しく解説

小云云
小云云オリジナル
2018-03-13 18:15:581143ブラウズ

js にはいくつかの基本的なデータ型と、(オブジェクト、配列、関数) などの他の複雑なデータ型があることがわかっています。基本的なデータ型の割り当ては、実際には値のコピーと呼ばれます。割り当てられた変数は同じです。元のとおり 変数には、値が等しいという以外の関係はありません。

let x = 666
let y = x
let m = 'abc'
let n = m
y = 888
n = 'def'
console.log(x, y)//666,888
console.log(m, n)//'abc','def'

複素データ型の転送はこのようにはなりません。変数が複素データ型にバインドされている場合、記録されるのは複素データの値ではなく、データのアドレス情報であるときです。この変数は別の変数に割り当てられており、実際にはデータ情報を指します。この転送メソッドは、参照渡し

let obj1 = {
    a : '1',
    b : 2
} 
let obj2 = obj1
obj2.b = 3
console.log(obj1,obj2)//{a: "1", b: 3},{a: "1", b: 3}

Copy

と呼ばれます。

複雑なデータ型の割り当ては参照によって行われることがわかっています。実際のプロジェクトでは、これが起こることは望ましくないことがよくあります。

には 2 つのビューがあります。ビュー データ (配列) はどこでも使用されます。まず、リストはデータを順番に表示するだけで済みます。次に、グラフはデータを処理する前にデータを反転する必要があります。 reverse() の後の最初のリストも逆の順序になっています。これは、配列の値をコピーするだけのメソッドが必要であり、この新しい配列のデータ アドレスは異なります。このコピー メソッドは、配列のコピーと呼ばれます。

let obj1 = {a:1, b:{c:2}}
let shallowCopy = (src)=> {
    let dst = {}
    for (let prop in src) {
        if (src.hasOwnProperty(prop)) {
          dst[prop] = src[prop]
        }
    }
    return dst
}
let obj2 = shallowCopy(obj1)
console.log(obj1,obj2) //@1
obj1.a = 6
console.log(obj2.a) //@2
obj2.b.c = 666
console.log(obj1.b.c) //@3
obj2.b = {
    c: 888
}
console.log(obj1.b.c) //@4

上記の例から、obj1 の最初の層の属性は属性値をコピーし、アドレスのコピーを継承しないことがわかりますが、2 番目の層の b 属性はメモリ アドレスを共有します。浅いコピーですが、@4 では、属性 b がオブジェクトであるため、obj1 は obj2 の影響を受けません。参照転送によるこの種の再割り当てにより、コンピュータはデータを保存し、アドレス情報を記録するために新しいメモリを再割り当てします。そのため、この時点では obj1 です。 .b.c と obj2。b.c はレコードの属性値ではなくなりました

次のように理解することもできます。複雑なデータ型の直接割り当ては参照転送であり、コピーは単純なデータ バックアップとは言えません。コピーも浅いコピーと深いコピーに分かれているため、元のデータのメモリアドレス情報は完全に同じではありません。

複雑なデータ型のネストされていないコピーは、データ情報の最初の層のみがコピーされることを意味します。これは、データの最初の層が複雑なデータ型を持つ場合でも、参照転送方法が使用され、コピーはまだアドレス情報であり、他のメソッドで実装された配列オブジェクトなどの多層ネストされたコピーがディープコピーです。 Arraysとオブジェクトが深いコピーと浅いコピーをどのように実装するかを見てみましょう。演算子

let arr1 = [1,2,[3,4]]
let arr2 = arr1.slice(0)
arr2[2].push(5)
arr2.push(6)
console.log(arr1,arr2)

上記 4 種類の配列のコピーはすべて浅いコピーであり、配列の深いコピーを実現するには、再帰的に実装する必要があります

let arr1 = [1,2,[3,4]]
let arr2 = arr1.concat()
arr2[2].push(5)
arr2.push(6)
console.log(arr1,arr2)
    メソッド arr1[2] と arr2 がわかります。 [2] は異なり、上記と同じディープコピー copy メソッドはオブジェクトにも適用できます
  • オブジェクトのコピー

  • ユニバーサル for ループ

    let arr1 = [1,2,[3,4]]
    let arr2 = []
    for(let i = 0; i<arr1.length; i++){
        arr2.push(arr1[i])
    }
    arr2[2].push(5)
    arr2.push(6)
    console.log(arr1,arr2)
  • ...operator

    let arr1 = [1,2,[3,4]]
    let [...arr2] = arr1
    arr2[2].push(5)
    arr2.push(6)
    console.log(arr1,arr2)
  • Object.assign( )

    let deepClone = (src)=> {
        let result
        (src instanceof Array) ? (result = []) :(result = {})
        for (let key in src) {
            result[key] = (typeof src[key] === 'object') ? deepClone(src[key]) : src[key]//数组和对象的type都是object
        }
        return result
    }   
    let arr1 = [1,2,[3,4]]
    let arr2 = deepClone(arr1)
    arr2[2].push(5)
    arr2.push(6)
    console.log(arr1,arr2)

上記の 3 つのメソッドはオブジェクトです 浅いコピー、オブジェクトのディープ コピーの 2 つのメソッドを紹介します:

文字列に変換してからオブジェクトに戻す

let obj1 = {a:1,b:{c:2}}
let obj2 = {}
for(let key in obj1){
    obj2[key] = obj1[key]
}
obj1.b.c = 6
console.log(obj1,obj2)
  • deepClone メソッドは、上記の配列

  • 関連概念

  • 純粋関数
  • 1つの入力から一意の出力を返し、外部環境に影響を与えない関数は、純粋関数と呼ばれ、その中で定義された変数は、によって再利用されます。関数が戻った後のガベージ コレクション メカニズム。

  • ただし、関数のパラメータが配列、オブジェクト、または関数の場合、参照が渡され、その操作が元のデータに影響を与えるため、この方法で記述された関数には副作用があり、可読性が低くなります。

影響を軽減する方法は、受信パラメータのディープ コピーを作成し、それらを新しい変数に割り当てて、元のパラメータが改ざんされるのを防ぐことです。

    純粋関数の例を見てみましょう:
  • let obj1 = {a:1,b:{c:2}}
    let {...obj2} = obj1
    obj1.b.c = 6
    console.log(obj1,obj2)

    上記の例を通して、wang の値が純粋関数によって変更されていないことがわかります。

  • もう一度次の例を考えてみましょう。正解した場合は、この記事の内容を深く理解していることになります(全員にリマインド -> 参照の再割り当て)
  • let obj1 = {a:1,b:{c:2}}
    let obj2 = Object.assign({},obj1)
    obj1.b.c = 6
    console.log(obj1,obj2)

    上記は私の js の理解です。ご理解ください。引用と送信が不適切な場合はご容赦ください。

  • 理解を深めたい場合は、他の記事もお読みください。JavaScript における価値とリファレンスの説明をお勧めします。

プロジェクト内で、ある変数を別の変数に代入する場面はよくありますが、これは実際にはjsの転送ですが、データ型が異なれば代入後の動作も​​異なります。一緒に勉強してみましょう。

传递

我们知道js有几种基本的数据类型和其他的复杂数据类型包括(对象,数组,函数),基本数据类型的赋值其实就是值的拷贝,我们称之为值传递,赋值后的变量和原来的变量除了值相等之外并无其他关联

let x = 666
let y = x
let m = &#39;abc&#39;
let n = m
y = 888
n = &#39;def&#39;
console.log(x, y)//666,888
console.log(m, n)//&#39;abc&#39;,&#39;def&#39;

复杂数据类型的传递并不是这样子的,因为将一个变量绑定到一个复杂数据类型的时候记录的并不是这个复杂数据的值,而是存放了这个数据的一个地址信息,当将这个变量赋值给另一个变量的时候只是将地址传递了过去,这两个变量指向的其实是一个数据信息,当改变任意一个变量的时候,另外的变量都会受到影响,这种传递方式我们称之为引用传递

let obj1 = {
    a : &#39;1&#39;,
    b : 2
} 
let obj2 = obj1
obj2.b = 3
console.log(obj1,obj2)//{a: "1", b: 3},{a: "1", b: 3}

拷贝

我们知道复杂数据类型的赋值是引用传递,赋值前后的变量会相互影响,在实际项目中我们经常不希望这样子,譬如:

我们在一个view里面有两处用到了data(是一个Array),其一是一个list只要把data按顺序展示即可,其二是一个chart需要把data逆序之后再做数据处理,这时候我们就犯难了,如果直接data.reverse()之后第一处的列表也是逆序的了,这不是我们想见的,我们需要一个方法只复制数组的值,并且这个新数组和原数组的数据地址并不一样,这种复制方式我们称为数组的拷贝。

let obj1 = {a:1, b:{c:2}}
let shallowCopy = (src)=> {
    let dst = {}
    for (let prop in src) {
        if (src.hasOwnProperty(prop)) {
          dst[prop] = src[prop]
        }
    }
    return dst
}
let obj2 = shallowCopy(obj1)
console.log(obj1,obj2) //@1
obj1.a = 6
console.log(obj2.a) //@2
obj2.b.c = 666
console.log(obj1.b.c) //@3
obj2.b = {
    c: 888
}
console.log(obj1.b.c) //@4

上面的例子可以看出来obj1的第一层属性是复制属性值,没有继承地址的拷贝,但是第二层就是b属性确实共享一块内存地址的,这就是浅拷贝,但是在@4处obj1却没有收到obj2的影响,是因为属性b是一个对象,这种引用传递的重新赋值,计算机会重新分配一块新的内存来存放数据和记录地址信息,所以这时obj1.b.c和obj2.b.c已经不是记录的一个属性值了

也可以理解为:拷贝是之于传递的,直接对复杂数据类型进行赋值是引用传递,不能称之为拷贝,拷贝是对原数据的单纯的数据备份,数据的内存地址信息并不完全一样,这是因为拷贝还分为浅拷贝和深拷贝。

对复杂数据类型的非嵌套的拷贝,就是只拷贝第一层的数据信息的拷贝是浅拷贝,如果第一层的数据有复杂数据类型,则依然采用引用传递的方式,复制的仍然是地址信息,通过其他方式实现的数组对象等的多层嵌套拷贝就是深拷贝。

下面我们再来看下数组和对象如何来实现深浅拷贝:

数组的拷贝

  • slice方法

    let arr1 = [1,2,[3,4]]
    let arr2 = arr1.slice(0)
    arr2[2].push(5)
    arr2.push(6)
    console.log(arr1,arr2)
  • concat方法

    let arr1 = [1,2,[3,4]]
    let arr2 = arr1.concat()
    arr2[2].push(5)
    arr2.push(6)
    console.log(arr1,arr2)
  • for循环

    let arr1 = [1,2,[3,4]]
    let arr2 = []
    for(let i = 0; i32bfcfa2ad8973d97a5666fc1acc4e11 {
        let result
        (src instanceof Array) ? (result = []) :(result = {})
        for (let key in src) {
            result[key] = (typeof src[key] === 'object') ? deepClone(src[key]) : src[key]//数组和对象的type都是object
        }
        return result
    }   
    let arr1 = [1,2,[3,4]]
    let arr2 = deepClone(arr1)
    arr2[2].push(5)
    arr2.push(6)
    console.log(arr1,arr2)

    可以发现用方面的方法arr1[2]和arr2[2]不一样,同样上面的深拷贝的方法也适用于对象

    对象的拷贝

    • 万能的for循环

      let obj1 = {a:1,b:{c:2}}
      let obj2 = {}
      for(let key in obj1){
          obj2[key] = obj1[key]
      }
      obj1.b.c = 6
      console.log(obj1,obj2)
    • …运算符

      let obj1 = {a:1,b:{c:2}}
      let {...obj2} = obj1
      obj1.b.c = 6
      console.log(obj1,obj2)
    • Object.assign()

      let obj1 = {a:1,b:{c:2}}
      let obj2 = Object.assign({},obj1)
      obj1.b.c = 6
      console.log(obj1,obj2)

    上面3种方法是对象的浅拷贝,再介绍2种对象的深拷贝的方法:

    • 转为字符串再转回对象

      let obj1 = {a:1,b:{c:2}}
      let obj2 = JSON.parse(JSON.stringify(obj1))
      obj1.b.c = 6
      console.log(obj1,obj2)
    • deepClone方法,就是上面的数组的deepClone方法

    相关的概念

    纯函数

    给定函数一个输入返回一个唯一的输出,并且不对外部环境附带任何影响的函数我们称为纯函数,其内定义的变量在函数返回后都会被垃圾回收机制回收掉。

    但是如果函数的参数是数组、对象或函数时,传入的是一个引用,对其操作会影响到原来的数据,这样子写的函数会产生附带的影响,使得可读性变低。

    降低影响的方式就是对传入参数进行深度拷贝,并赋给一个新的变量,方式原来的参数被篡改。

    我们来看一个纯函数的例子:

    let pureFunc = (animal)=> {
        let newAnimal = JSON.parse(JSON.stringify(animal))
        newAnimal.type = &#39;cat&#39;
        newAnimal.name = &#39;Miao&#39;
        return newAnimal
    }
    
    let wang = {
        type: &#39;dog&#39;,
        name: &#39;Wang&#39;
    }
    
    let miao = pureFunc(wang)
    console.log(wang,miao)

    通过上面的例子可以看到wang并没有被纯函数所改变值。

    大家再来思考一下下面的例子,如果答对了说明你已经对本文所讲的东西有了很深刻的理解了(提醒大家一下—>引用的重新赋值)

    let afterChange = (obj)=>{
        obj.a = 6
        obj = {
            a: 8,
            b: 9
        }
        return obj
    }
    let objIns = {
        a: 1,
        b: 2
    }
    
    let objIns2 = afterChange(objIns)
    console.log(objIns, objIns2)

    以上就是我对js的引用和传递的理解,如有不当之处敬请谅解,Thanks!

    相关推荐:

    js函数的按值传递参数

    JavaScript参数传递图解教程

    js函数参数的按值传递解释

以上がjsでの受け渡しとコピーについて詳しく解説の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。