Go to comments

JavaScript 复习

一、包装类

javascript的值一般分两种,一种是原始值,一种是引用值


引用值

引用值就是一种对象,包括数组、函数、和对象等等一系列引用值,引用值都算概括类的对象,泛泛的,

对象才可以有属性和方法,并且可以灵活的调用和赋值。


原始值

原始值之所以不是引用值,

第一,它的存储方式是存在栈内存里面的,

第二,它是不可以有属性和方法的


原始值和引用值两大区别,

1.存储地址不一样,2.原始值不能有属性和方法,引用值才可以有,

现在有一个问题,javascript的一些原始值,可以像引用值一样的去拿它的属性并赋予一些属性,这是为什么呢?


比如 str 不可能有 length 属性,然而它就能使用 length 属性

var str = "abc";

console.log(str.length); // 3

变量 str 是原始值,原始值怎么可能有属性呢?

这是javaScript内部给它一个新的机制,这个机制叫做包装类。


原始值确实是没有属性的,但是你要想通过原始值访问属性的话,js会让你这个语法好使,怎么好使的呢?

1. 当原始值调用这个 length 属性的话,js会对应的看这个原始值str是什么类型的,

    str是"字符串"类型的

2. 只要是字符串类型的原始值,js就会  new String() ,这个 String() 是一个字符串对象的构造函数,

    字符串身上有一个原始值,也有一个引用值, js会  new String()  构造出一个字符串对象出来,

3. 然后把字符串对象的内容填成abc  new String( 'abc' )  和原来  str = 'abc'  字符串保持一致

4. 构建出这样一个对象来之后,js看对原始值进行了一个  str.length  的操作,

5. 那系统会相应的对它新构造出的对象,进行一个 length 操作 new String('abc').length  ,并且把这个结果返回到  str.length 


实际上写的是  str.length ,其实执行的时候是  new String('abc').length 

 str.length   --执行-->   new String('abc').length 

这个隐式包装的过程就叫做都包装类


为什么管它叫包装类呢?

这个  new String('abc')  是不是相当于把  str = 'abc'  给包裹起来了,

原来是个原始值,js给包成一个对象了,这样一个过程就管它叫做包装类,名字就是这么叫的。这是其一,其二呢?


其二,来一个数字,给数字加一个属性abc

var num = 123;

num.abc = 'abc';

正常原始值能有属性吗?

不可能是吧,强硬的操作了怎么办?系统不会报错,系统会在内部帮我们缓和一步,怎么缓和的呢?

1. 因为识别出是Number类型,系统会  new Number( num )  把 num 值放进去,

2. 然后生成一个数字对象,数字对象上可以有属性和方法,

    那就给字对象加上  new Number( num ).abc='abc' ,执行完这步之后,

3. 上面new 的 Number  new Number( num ).abc='abc'  没等到下一步就被销毁了,


因为这步  num.abc = 'abc'  操作js帮忙实现了,js没必要保留这个值就销毁了

var num = 123;
num.abc = 'abc';

// new Number(num).abc = 'abc' --> delete


销毁了之后下一步再想访问  num.abc  的时候会怎么样?

var num = 123;
num.abc = 'abc';

// new Number(num).abc = 'abc' --> delete

// 销毁了之后再访问num.abc

console.log(num.abc); // undefined

// new Number(num).abc 系统再new了一新数字对象,会把这次操作的结果放到num.abc里面。

4. js看又访问这个  num.abc ,js不能让操作报错啊,js又再一次  new Number(numnum值放进去,

5. 而这次的  new Number.(num).abc  和刚刚赋值的  new Number.( num ).abc='abc' 是两个独立的对象,

    虽然说长的一样但是彼此独立的,彼此独立的对象

6. 那么现在访问  new Number.(num).abc  跟上面的没有任何关系,因为上面的被销毁了,所以现在访问出的结果是 nudefined


问题维密天使

这两个是一样的

 num =new Number(123)  会返回一个值,把这个返回值保持到 num 里

var num = 123;

var num = new Number(num);

num.abc;

如果不想返回值保存到num里, 当场就用直接 new Number(123).abc  也行,因为会在原地返回一个对象出来,相当于  {}.abc 

var num = 123;

new Number(num).abc;


二、原型

原型必须是基于构造函数的,没有构造函数这个原型没有意义

Person.prototype.lastName = 'Glee';

function Person(){

}


任何一个函数包括构造函数(构造函数算函数),任何一个函数都会有一个 prototype 属性,这个prototype是这个函数构造出对象的公有祖先


公有祖先是什么意思?

祖先上的方法和属性,这个构造函数构造出的对象都能去用


 new Person  操作完后生成对象 LiLi,对象 LiLi 可以用  Person.ptototype 上的任何东西,包括这个 lastName

Person.prototype.lastName = 'Glee';

function Person(){

}

var LiLi = new Person();

console.log(LiLi.lastName); // Glee


为什么对象 LiLi 能访问 lastName?

因为在 new 的过程中,发生了隐式三步

1. 第一步在函数体内隐式的 this 等于一个空对象  this = {} 

2. this里面不是完全空的,它有一个  __proto__  属性

3.  __proto__  属性指向的是,这个 Person() 构造函数的原型  Person.prototype 

Person.prototype.lastName = 'Glee';

function Person(){
  // var this = {
  //   __proto__: Person.prototype
  // } 
}

var LiLi = new Person();

console.log(LiLi.lastName); // Glee

4. 然后访问 LiLi 对象的 lastName 属性的时候(LiLi.lastName),

5. 他先看自己身上有没有,如果没有,

6. LiLi对象会沿着它的  __proto__ ,找到 __proto__ 上的对象(person.prototype),上这个  person.prototype  对象上面找有没有我们想要的属性。

7. 如果  person.prototype  对象上面还没有,

8. 会沿着这个  person.prototype  对象上的 __proto__ 指向它的原型上去找,

    这样会形成一个原型链的访问顺序。


三、Object.create()

这个 Object.create() 方法也能创建对象出来,

只不过他创建对象比较灵活,这个 Object.create() 里面可以填上它创建出对象的原型,而且还必须得填,不填是不行的


 var obj = Object.create( 原型 )  


这个 Object.create() 创建出的对象,需要指定这个对象的原型是谁

var demo = {
  lastName: 'Glee'
}

var obj = Object.create(demo);

1. 把对象 demo 放到 Object.create(demo) 里面去,

    这样 Object.create 会创建一个空对象出来,这个空对象的原型就是demo

2. 也就是 Object.create(demo) 创建出的对象obj,obj里面的 __proto__ 指向的是demo

    obj = {

      __proto__: demo

    }

3. obj在自己身上找不到属性的时候,会到 demo 身上去找,因为 demo 是它的原型


其实这个 Object.create 还可以传第二个参数,第二个参数是填的属性的特性,

每个属性有四大特性,其中一个特性就是可配置性不可配置性,什么是可配置和不可配置性?


属性的可配置性和不可配置性

delete 操作符可以删除属性,

问一个问题  var num = 123  算不算一个对象的属性?

变量 num 算 window 对象的一个属性

var num = 123; 

console.log(delete num); // false

console.log(delete window.num); // false

这个num是window的属性,为什么这回就删不了num属性呢?

属性分两种构建方式,

1. 这种写到全局范围内 window 上的属性,

2  一旦经历了 var 的操作,所得出的属性( 就是window上的属性 ),

3. 这种属性叫做不可配置性属性,不可配置的属性delete不掉。


delete删除的属性只能是可配置的

var obj = {

}

obj.num = 234;

console.log(obj.num); // 234

console.log(delete obj.num); //true 这样的就能删除了

console.log(obj.num); // undefined 再看就没有了


什么是可配置的呢?

直接写  window.num = 123  这叫直接赋值,没经过 var 这个过程

window.num = 123;

console.log(delete window.num); // true

console.log(window.num); / /再看window.num就没有了返回undefined


现在  var num = 123 

var num = 123;

console.log(window.num); // 123

console.log(delete window.num); // false

console.log(window.num); // 再看window.num还能输出123


通过 var 给 window 上增加的属性叫做不可配置性属性,直接增加的叫可配置的,所有有 var 存在叫不可配置属性。


Object.create() 第一个参数填的是原型叫 prototype,第二个填的是特性 definedProperty,特性以后再说

var demo = {
  lastName : 'Glee'
}

var obj = Object.create(demo);

Object.create(prototype, definedProperty);


可枚举性可读可以写这些都是特性,

有的属性往里面写不进去值,不能往写但是能访问,这叫可读不可写,这些都是特性,

属性操作的一些特性,以后说这些问题,因为现在用不着,其实以后编程也用不着,但是确实有。


四、this和call的关系

this和call放到一起讲挺科学的,call能改变函数体里面的this指向


this的四大特点

1. 预编译 this指向--> window

2. 谁调用的 this指向--> 谁

3. call、apply改变this的指向

4. 全局 this--> window


1、预编译过程中this指向window

函数执行的时候要经过预编译过程,预编译过程生成一个AO

function test(){

  var num = 123;

  function a(){
  }

}

预编译过程

第一步,生成AO

第二步,找形参变量声明,把形参变量的名当 AO 对象属性的名

第三步,形参实参相统一

第四步,把函数a提升上去

test() --> AO{

    num: nudefined,

    a: function(){}

  }


然后都完事了之后,其实还有一步隐式的

1. 里面要设置一个 arguments 等于一个实参列表(arguments是一个类数组)

2. 然后还应该有一个this,每个函数体里面都会有this,这个this此时此刻指向的是window

test() --> AO{

    arguments: {},

    this: window,

    num: nudefined,

    a: function(){}

  }


此时此刻直接执行 test(),按照预编译的环节里面打印的应该是window(预编译 this指向--> window)

function test(){

  var num = 123;

  function a(){
  }

  console.log(this);
}

test(); // Window {parent: Window, opener: null, top: Window, length: 0, frames: Window, …}


2、而 call、applty 干的是一件什么事呢?

这么 test() 执行就完全等于 test.call() 这么执行,他两个是一样的,这样写 test.call() 打印的也是 window

function test(){

  console.log(this);

  var num = 123;

  function a(){
  }
}

test(); // Window {parent: Window, opener: null, top: Window, length: 0, frames: Window, …}
test.call(); // Window {parent: Window, opener: null, top: Window, length: 0, frames: Window, …}


test() 执行在内部就会转变成 test.call() 执行,

而 test.call() 如果手动往里面传值的话,第一个值就会作为这个函数执行时的一个this环境,换句话说它会函数执行时的 this 改变了。


比如现在往里面传一个参数传一个对象 {name : "LiLi"},这个对象就会放到预编译的this上

function test(){
  console.log(this);
}

test.call({name : "LiLi"}); // 1.call里面传一个对象,打印结果是{name: "LiLi"} 因为函数里面this指向的是这个对象

test() --> AO{

    arguments: {},

    this: {name: "LiLi"}, // 2. 对象就会放到预编译的this上

  }


所以再 call() 之后,在想在函数作用域里找this,this指向的是对象 {name : "LiLi"}

这是 call、applty 的一个作用


3、谁掉用的 this 就指向谁

想一个问题,

用   call( {name: "LiLi"} )  里面传值,是不是就相当于让这个  {name: "LiLi"}  对象调用这个 test 函数了,这是一样是的改变 this 的指向,相当于让 this 变成对象了。


写一个代码飘逸的代码

执行 obj.say() 是obj调用say,say里面的this是obj对象,打印this.name是obj

var name = 'window';

var obj = {
    name: "obj",
    say: function () {
    console.log(this.name);
  }
}

obj.say(); // obj


 obj.say.call( window )  这次执行有 call 的存在了,有 call 存在就打破原来一切规则,call里传的是谁,obj对象里面this就得是谁

var name = '我是window';

var obj = {
  name: "obj",
  say: function(){
    console.log(this.name);
  }
}

obj.say.call(window); // '我是window'

打印出的是'我是window'


再往后推

var name = '我是window';

var obj = {
  name: "obj",
  say: function() {
    console.log(this.name);
  }
}

var fun = obj.say; // 这句话的意思是把 obj.say 这个函数体拿出来了,只拿函数体不管谁是谁
// fun = function () {
//   console.log(this.name);
// };

fun(); // 然后后在外部执行fun(),相当于让这个函数在全局范围内自调用没有人调用它,打印'我是window'

虽然你是别人的函数,但我把你的这个函数体拿给我了,然后在外边自己执行,跟你 obj 没关系了,

没有人调用的也没有call,只能走预编译环节,预编译环境 this 就是 window,所以现在打印的是"我是window"字符串。


再改一下  fun.call( obj ) ,还是 fun 执行里面 this 变成 obj 了,打印出的是字符串"obj"

var name = 'window';

var obj = {
  name: "obj",
  say: function () {
    console.log(this.name);
  }
}

var fun = obj.say; 

fun.call(obj); // obj


4、call 和 apply最重要的一个问题,就是解决这样的一个函数借用问题

现在想用 Person 的方法,来实现 Student 里面的构造怎么办?

function Person(name, age){
  this.name = nam;
  this.age = age;
}

function Student(name, age, sex){
  // 这里面怎么写?
}

var LiLi = new Student("LiLI", 36, "female");


当然 Student 函数里面可以义无反顾的这样写,构造出来对象 LiLi 里面有 name, age, sex 都有

function Person(name, age){
  this.name = nam;
  this.age = age;
}

function Student(name, age, sex){
  this.name = name;
  this.age = age;
  this.sex = sex;
}

var LiLi = new Student("LiLI", 36, "female");


但是想简化一下,

既然构造函数 Person 已经抽象完了,想用 Person 函数实现自己的功能怎么写?


想用人家的 Person 函数,实现给 Strdent 函数自己的 this 加上属性,一旦new操作了Strdent就有自己的this

function Person(name, age){
  this.name = name;
  this.age = age;
}

function Student(name, age, sex){

  // var this = Object.create(Student.prototype) new操作后内部隐式的生成this对象

  this.name = name; // name、sex这两行代码调用Person的
  this.age = age;

  this.sex = sex;
}

var LiLi = new Student("LiLi", 36, "fomale");


想让构造函 Person 里面的this,代表构造函数 Student 的 this 只有 call 能办到

function Person(name, age){
  this.name = name;
  this.age = age;
}

function Student(name, age, sex){

  // var this = Object.create(Student.prototype) new操作后,准的内部引用是这样

  Person.call(this ,name, age); // 传Student构建好的this(隐式的this)放到Person里面,然后用Person的方法体实现我的功能
  this.sex = sex;

}

var LiLi = new Student("LiLi", 36, "fomale");

console.log(LiLi); // Student {name: "LiLi", age: 36, sex: "fomale"}


五、闭包

闭包的一个表象,是一个函数套着另一个函数,把被嵌套那个函数,给保存到套他函数的外部,

也就是a套着b,把 b 弄出 a 里面不管用什么方法弄出去,就必然形成闭包。

function a(){

  function b(){

  }

  return b;

}


还有什么方法能弄出去?有好多种

1. 定义一个对象obj

2. 让  obj.fun = b  ,函数直接到对象obj里面了,b函数出去了到 obj 对象里面去了

var obj = {};

function a(){

  var aa = 123;

  function b(){
    console.log(aa);
  }

  obj.fun = b;

}

a(); // 1.a()函数执行完后就销毁了,a销毁了不要紧

obj.fun(); // 2."obj.fun()"能执行打印123,因为b保持到obj上

3. 这个a执行完后这个b还好好的呢,

    这样b函数就会拿到a函数执行期上下文死死不放,这就形成闭包了,

4. 自此之后b照样能访问a里面的东西,未必非得return,只要被保存到外部就行


问题天使

别太把"立即执行函数"不当成函数,它也是函数,

它就有一个区别,就是执行完别人找不着它了,立即执行函数读到它立即执行,执行完后别人找不到它了。


问题天使

预编译发生在什么时候?

预编译发生在执行的前一刻,先预编译后,后执行函数体

/*---------*

GO{
  A: 123
}

AO{
  a: 1
}

*---------*/


var a = 123;

function test(){
  a = 1;
  var a;
}

test();

所以预编译好了之后就有一个AO了,AO里面有一个"a:  undfined"

然后在一个函数里访问一个变量,捋着作用域链找,作用域链最靠近它的就是自己的AO,

自己的AO上已经有变量a了,就不去全局上找了,

尽管在全局GO里面也有一个变量a,但操作的还是自己AO里面的a,因为自己的AO里面有a,当在自己AO里找不到a,才到全局上找。


问题天使

可以把  object.prototype  理解成一个变量,属性就是一个变量,属性里面存的引用值,存的都是存的引用值的地址,

obj = {
  // __proto__ : object.prototype
}

栈内存里面放的都是地址,通过栈内的地址找到堆内存才能拿到属性值。


问题天使

函数什么时候 new?什么时候不 new?为什么要 new?

我们想通过这个"构造函数"构造对象时候才用new,我们想执行函数的时候就不用new。


Person 是构造函数,是为了构造对象的,那么怎么来构造对象?

function Person(){

  this.name = 'abc';

  this.age = 123;

}

var LiLi = new Person(); // 必须是new加上构造函数的执行,才能构造对象出来


构造对象有个规则,必须是 new 加上构造函数的执行(new Person())才能构造对象出来。


因为有了new这个操作符之后,这个构造函数会发生三步隐式的变化(其实是两步)

第一步:var this = {}

第二步:return this

function Person(){

  // var this = {} 第一步

  this.name = 'abc';
  this.age = 123;

  // return this; 第二步

}

var LiLi = new Person(); // 生成对象发生隐式二部


new 加 函数执行才能出现这隐式的两步,有了这隐式两步之后这个对象才能被正式的构造出来。


而正常执行 Person() 只会走预编译,new也会走预编译,正常执行不会有那两部隐式的,这时候this指向的是window

function Person(){

  this.name = 'abc';
  this.age = 123;

}

Person(); // 执行里面每一行代码,this指向window

正常封装一个函数,这个函数只代表一个功能的时候,跟new没有关系,因为只想执行函数里面的每一句。
想通过这个构造函数构造对象,必须得new,不new生成不了对象,new只是为了生成对象的。


问题天使婉絮

闭包形成私有化变量

Person是一个构造函数,想通过Person构造出一个对象出来,

1. 这个对象有一些可以被外部访问的属性(name)

2. 还有一些不能被外部访问的属性(money)

3. 有几个方法 this.makeMoney()、this.offer()消费、this.show()

function Person(name){

  var money = 100; // 2.不可以被外部访问的属性

  this.name = name; // 1.可以被外部访问的属性

  this.makeMoney = function(){
    money ++;
  }

  this.offer = function () {
    money --;
  }
  
  this.show = function(){
    console.log(money);
  }

}

var LiLi = new Person();

有时候构造出来的对象,这个对象并不是所有属性和方法都想被外部访问,

因为有的时候,我们构造出的对象要直接传给别人去用,别人并不知道对象里有什么东西,

所以为了保证对象的一些属性的私有性,因为这个属性是很关键的,不想让人改,一改可能整个对象就崩溃了。


不想让人改,只给别人留了能访问这个对象的一些,基本的属性和基本的方法的接口,什么意思?

1. this.name、this.makeMoney()、this.offer()这些都是能访问的,这些都是能给我们提供一些基本数据的功能

2. 而内部真正产生运作枢纽化的变量不给你,比如  money=100  很多方法都是围着它转的,

3. 这个 money 就是不给别人显示出来,但是别人就是能用,不能直接修改noney,

    只能通过 this.makeMoney() 方式、this.offer() 的方式来改,这种 noney 属性就叫做私有化属性。


实现私有化属性的基础理论,就是通过闭包的形式,怎么样通过闭包实现呢?

1.  new Person()  相当于在函数的顶端,隐式的声明this等于一个空对象  var this = {} 

function Person(name){

  // 1. new Person()相当隐身的声明this等于一个空对象
  // var this = {
  // }

  var money = 100;

  this.name = name;

  this.makeMoney = function(){
    money++;
  }

  this.offer = function(){
    money--;
  }

  this.show = function(){
    console.log(money);
  }

}

var LiLi = new Person();


2. this 空对象上有一些属性  name: name makeMoney: function(){} offer: function(){} 

function Person(name) {

  // 2. this空对象上有一些属性
  // var this = {
  //   this.name: name,
  //   this.makeMoney: function() {},
  //   this.offer: function() {}
  // }

  var money = 100;

  this.name = name;

  this.makeMoney = function() {
    money++;
  }

  this.offer = function() {
    money--;
  }

  this.show = function() {
    console.log(money);
  }

}

var LiLi = new Person();


3. 最后把this返回,就是把this等于的对象返回了,

    而对象上有两个方法 this.makeMoney()、this.offer(),这两个跟着对象一起被返回到外部,

4. 这两个方法就和之前 Person 产生的执行期上下文,产生一个公有的闭包,二对一的闭包,

function Person(name) {

  // var this = {
  //   this.name: name,
  //   this.makeMoney: function() {},
  //   this.offer: function() {}
  // }

  var money = 100;

  this.name = name;

  this.makeMoney = function() {
    money++;
  }

  this.offer = function() {
    money--;
  }

  this.show = function() {
    console.log(money);
  }

  // return this;
  // 3. 最后把这个this返回,就是把这个对象返回了this对象 this = {},
  //    而对象上有两个方法,这两个方法跟着this对象一起被返回到了外部

}

var LiLi = new Person();

LiLi.offer(); // 100 - 1
LiLi.show(); // 99
LiLi.offer(); // 99 - 1
LiLi.offer(); // 98 - 1 
LiLi.show(); // 97

5. 这个两个方法能操作的Person的AO是一个人,这样他俩操作的都是一个money,

6. 这个money就是Person到AO里面的,而Person的AO被拴到了 makeMoney()、toffer() 上,

7. 一旦通过makeMoney()、offer() 里面去访问里面的变量 money,他会走自己的作用域链,

    自己的作用域先找自己的,没有再找Person的AO,因为他们之间形成链了,这样形成一个私有化money属性


总结,

就是变量money保存在函数person里面,看不见摸不着,但真实存在,而且外部不可以访问,

外部很难说 LiLi.money,因为没有,因为 money 在 LiLi 方法的作用域链里,在 LiLi.makeMoney() 的作用域链里,

想通过对象 LiLi 访问不到 money 属性,但是对象 LiLi 确实可以操作 money,

这个变量 money 就作为对象 LiLi 的一个私有化变量存在。


简单的说就形成闭包,是闭包的一个应用,

如果有一天,我们我们自己编写一个构造函数,构造函数想用一些私有化的变量当做中传(中枢)的,

这些变量又不想让别人直接访问到,那就把变量放到闭包里好了。


包括圣杯模式

var inherit = (function(){

  var F = function (){};

  return function(Target, Origin){
    F.prototype = Origin.prototype;
    Target.prototype = new F();
  }

}());

立即执行函数会返回东西,return保存出来之后是这样子的

var inherit = function(Target, Origin){

    F.prototype = Origin.prototype;  // 返回后F.prototype的F还能被调用吗?

    Target.prototype = new F();

}

但是,返回出来之后F还能被调用吗?

能被调用,因为作为内部函数,保存了立即执行函数的执行期上下文,会把这个F保存出来。
而这个  var F = function(){}  只作为中间的一个过渡的东西,它并没有什么实际性的意义,所以没必要让用户看到它,它作为一个私有化变量也是合情合理的。


六、引用值的类型转换

空数组加上一个空字符串,等于一个空字符串

console.log([] + ""); // ""

空数组加上数字1等于1。

console.log([] + 1); // "1"

空数组加上数字2等于2

console.log([] + 2); // "2"

Number( [] ) 里面放空数组等于 0

console.log(Number([])); // 0

空数组减1等于负一 -1

console.log([] - 1); // -1

空数组减1实现了,也说说明内部隐式调用了Number( [] ) 


引用值不用参与隐式类型转化,但是引用值能进行类型转化。

空数组减1调用了Number


空数组加1等于1,其实调用的是String( [] )String( [] ) + 1,就是引用值放在加号左侧,调用的是隐式类型转换的String

console.log([] + 1); // 1

String转换空数组

console.log(String([])); // 0

空对象+1,调用的是隐式类型转换的String()

console.log({} + 1);  // [object Object]1

String转行空对象

console.log(String({})); // [object Object]

Number转换空对象

console.log(Number({})); // NaN

没有必要研究这个东西,对象加1、对象减1干哈啊没必要!


我们只需要知道:

空数组等于空数组吗?不等于返回false

console.log([] == []);  // false

console.log([] === []); // false

为什么不等于?

因为每个引用值都有一个独立的地址,虽然两个引用值长相一样但是地址不同,地址不同得出的结论是false,

更不可能绝对等于

引用值的类型转换不用去考虑


七、深度克隆

浅度克隆的原理就是,把obj的属性全部都遍历一遍,然后挨个的放到obj1

var obj = {
    name : "abc"
}

var obj1 = {

}

for(var prop in obj){

    obj1[prop] = obj[prop];

}

浅克隆有一个问题,当拷贝引用值的时候就不行

var obj = {
    name : "abc",
    card : ['visa', 'master']  // card是一个引用值
}

var obj1 = {

}

for(var prop in obj){
    obj1[prop] = obj[prop]; // 到引用值这就相当于obj1['card'] = obj['card']
}

obj1['card'] = obj['card']  浅克隆拷贝就是相当于,把引用值的地址给另一个地址了,

它两个指向的是同一个空间,这样会造成一个现象,你改我们也改。


不想造成这个现象怎么办呢?

采取一个深度克隆的形式

var obj = {
	name: "abc",
	wife: {
		name: 'xialiu',
		son: {
			name: 'xiaodeng'
		}
	}
}

var obj1 = {};


/*--------------------------------------------------------------

拷贝原始值name: "abc"就直接拷贝过去了,拷贝引用值怎么办?

"深度拷贝"处理的就是引用值,
因为引用值直接拷贝不行,怎么处理?

01/
先分析,是引用值,还是原始值?
遍历到wife是引用值,到底是哪种引用值?是对象就新建一个对象{}

var obj1{
    wife : {} // 新建一个空对象{}
}

02/
打开原有对象看看里面有什么值,
第一个"obj.wife.name"是原始值,原始值就直接拿过来就行

var obj1{
    wife : {
        name : 'xialiu', // 原始值就直接拿过来
    } 
}

03/
然后,看到第二个是引用值,引用值不能直接拷贝,怎么办?
直接新建空对象,再分析原来son里面有什么东西

var obj2 = {
    wife : {
        name : 
        son : {} // 新建空对象,再分析son里面有什么
    }
}

04/
拿son里面的obj.wife.son.name
最后构建完的是这样

var obj2 = {
        name :      // obj.name
    wife : {
        name :        // obj.wife.name
        son : {
            name:  // obj.wife.son.name
        }
    }
}

原始值是直接拿的,引用值是自己新建一个空对象

--------------------------------------------------------------*/


问题天使

undefined为什么不大于0,小于0,等于0?

大于号,小于号和数学运算符号不一样。

null能转换成Number等于0

Number(null); // 0

null和0比应该是等于0的吧,其实它没那么转换,null和0比null他不转换,系统规定不转换,他俩就是不能和数字进行比较这是规定。

consoel.log(null > 0); // false

consoel.log(null < 0); // false

consoel.log(null == 0); // false

undefined和0比也不转换

consoel.log(undefined > 0); // false

consoel.log(undefined < 0); // false

consoel.log(undefined = 0); // false

undefined和null就是不能和数字比较,null、undefined不作为比较值存在

console.log(null < 1); // 返回false

他俩null、undefined也是原始值,

null类型里只有null,

undefined类型里只有undefined,

他俩代表是自己类型的原始值和0去比较的,他俩就是不能比较运算。


八、预编译

js执行分为三步,

1). 第一步语法/语义分析,意思是先通篇扫描全局,

     看看有没有什么低级语法错误(多些括号,少些逗号,写没写汉字什么的),

2). 然后是预编译,

3). 然后最后是执行


也就是先预编译后再执行,在函数执行前一刻先发生预编译,预编译有四步

function test(a){

    var a = 123;

    var b = 234;

    function a (){

    }
}

test(1);

预编译有四步


第一步:

生成一个AO{}对象,

这个对象叫"Activation Object"翻译过来叫执行期上下文,

这个行期上下文里面存储了由于函数产生的一些列东西。

AO{

}


第二步:

找函数里面的变量声明 和 形参

形参也相当于一个变量声明,由于形参名的a,变量声明已经有了,就不用多写了,因为对象里面也不可能有多个属性名是一样的,

把函数里面的"变量声明"的名,作为AO对象的属性名值是undefined。

AO{

a : undefined, 

b : undefined

}


第三步:

形参和实参相统一,

把实参赋到a里面去

AO{

a : 1, 

b : undefined

}


第四步:

通篇找函数声明,

把函数声明的名作为AO对象的属性名,

属性名是a,值是函数体,就把原来的"a = 1"给覆盖了

AO{

a : function a(){}, 

b : undefined

}


这样一个AO对象就构建完了,AO干什么的呢?

接下来函数执行,

函数执行过程中,所访问的一切变量,所修改的一切变量,从哪里来?

从AO里来。


01/

函数执行的第一步 var a = 123(不用看var已经提升了)

"a = 123"这个到作用域链里面去找a,作用域链最顶端就是AO

a现在是函数,不好意思把a变成 a = 123

AO{

    a : 123, 

    b : undefined

}


02/

然后在找b = 234

b等于234,b现在是undefined,赋成b = 234

AO{

    a : 123, 

    b : 234

}

   

这是一个预编译的整体流程



Leave a comment 0 Comments.

Leave a Reply

换一张