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(num) 把num值放进去,
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( [] )加1 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
}
这是一个预编译的整体流程