本系列将从如下专题去总结:javascript
1. JS基础知识深刻总结
2. 对象高级
3. 函数高级
4. 事件对象与事件机制前端
暂时会对以上四个专题去总结,如今开始JS之旅的第一部分:JS基础知识深刻总结。下图是我这篇的大纲。 java
话在前面:我一直都认为,在互联网学习的大环境下,网路学习资源不少,但这些博客等仅仅是一个向导,一个跳板,真正学习知识仍是须要线下,须要书籍。 另外,若有错误,请留言或私信。一块儿成长,谢谢。git
基本类型 | 类型的值 | 检测方法 |
---|---|---|
Number | 能够任意数值 | 用typeof检测结果为number |
String | 能够任意字符串 | 用typeof检测结果为string |
Boolean | 只有true/false | 用typeof检测结果为boolean |
undefined | 只有undefined | 用typeof检测数据类型和‘===’(全等符号) |
null | 只有null | ‘===’(全等符号) |
Symbol | 经过Symbol()获得,值可任意 | 用typeof可检测结果为symbol |
对象类型 | 描述 | 检测方法 |
---|---|---|
Object | 能够任意对象 | 能够用typeof/instanceof检测数据类型 |
Array | 一种特别的对象(有数值下标,并且内部数据是有序的。通常的对象内部的数据是无序的,好比你一个对象中有name和age,他们是无序的。) | instanceof |
Function | 一种特别的对象(能够去执行的对象,内部包含可运行的代码。一个普通的对象能够执行吗?不能。)另外,对象是存储一些数据的,固然函数也是存储一些代码数据。 | typeof |
Date | 时间对象 | instanceof |
RegExp | 正则对象 | instanceof |
基本数据类型 | 对象类型 |
---|---|
基本类型的值是不可变的 | 引用类型的值是可变的 |
基本类型的比较是它们的值的比较 | 引用类型的比较是引用(指针指向)的比较 |
基本类型的变量是存放在栈内存(Stack)里的 | 引用类型的值是保存在堆内存(Heap)中的对象(Object) |
typeof 注1:用typeof
判断返回数据类型的字符串(小写)表达。好比:typeof ‘hello’
结果是 string
。 注2:用typeof
来测试有如下七种输出结果:number
string
boolean
object
function
symol
undefined
。 所以typeof不能去判断出null
与object
,由于用typeof
去判断null
会输出object
。 注3:全部的任何对象,用typeof
测试数据类型都是object
。所以,typeof
不能去判断出object
与array
。github
===(全等符号) 注1:只能够判断undefined 和 null 由于这两种基本类型的值是惟一的,便可用全等符比较。面试
instanceof 注1:A instanceof B
翻译就是B的实例对象是A 吗? 判断对象的具体类型(究竟是对象类型中的Object
Array
Function
Date
RegExp
的哪个具体的类型),返回一个Boolean值。ajax
借调法:Object.prototype.toString.call()
注1:这种方法只能够检测出内置类型(引擎定义好的,自定义的不行),这种方法是相对而言更加安全。Object
Date
String
Number
RegExp
Boolean
Array
Math
Window
等这些内置类型。算法
以上说明都有案例在面试题里json
var obj={ name:'lvya' };
console.log(obj.age); //undefined
console.log(age); //报错,age is not defined
复制代码
从这个点再去看一个简单的例子:function Person(name,age,price) {
this.name = name
this.age = age
this.price=price
setName=function (name) {
this.name=name;
}
}
var p1 = new Person('LV',18,'10w')
console.log(p1.price); // 10w
复制代码
根据上面这个例子,问个问题。请问访问p1.price
先找啥?后找啥?经过啥来找?(问题问的很差,直接看答案吧) An:p1.price
先找p1
后找 price
。 p1是一个全局变量哦,这个全局变量自己存在栈内存中,它的值是一个地址值,指向new Person
出来的对象。怎么找呢?先找p1是沿着做用域找的,后找price是沿着原型链找的。这就是联系,从另一个方面细看问题。可能这样看问题,你就能够把原型链和做用域能够联系起来思考其余问题。串联知识点:请你讲讲什么是原型链和做用域链? 咱们从a.b这个简单的表达式就能够看出原型链和做用域链。(a正如上例的p1)第一步先找a!a是一个变量,经过做用域链去查找,一层一层往外找,一直找到最外层的window,还没找到那就会报错,
a is not defined
。 找到a这个变量,它的值有两种状况:基本数据类型和对象类型。 若是是基本数据类型(除了undefined和null)会使用包装类,生成属性。若是是undefined和null就会报错,显示不能读一个undefined或null的属性。 若是是对象类型,这就是对象.属性的方式,开始在对象自身查找,找不到沿着原型链去找。原型链也找不到的时候,那么就会输出undefined
。数组
问题2:undefined与null的区别? undefined
表明定义未赋值 nulll
定义并赋值了, 只是值为null
var a;
console.log(a); // undefined
a = null;
console.log(a); // null
复制代码
使用Object.prototype.toString.call()
形式能够具体打印类型来区别undefined和null。 若是值是undefined
,返回“[object Undefined]”。 若是这个值为null
,则返回“[object Null]”。
问题3:何时给变量赋值为null 呢? 初始赋值, 代表这个变量我将要去赋值为对象 结束前, 这个对象再也不使用时,让对象成为垃圾对象(被垃圾回收器回收)
//起始
var b = null // 初始赋值为null, 代表变量b将要赋值为对象类型
//肯定赋值为对象
b = ['lvya', 12]
//结束,当这个变量用不到时
b = null // 让b指向的对象成为垃圾对象(被垃圾回收器回收)
// b = 2 //固然让b=2也能够,但不常使用
复制代码
问题4:变量类型与数据类型同样吗? 数据的类型:包含基本数据类型和对象类型 变量的类型(实则是变量内存值的类型) JS弱类型语言,变量自己是无类型的。包含基本类型
: 保存的就是基本类型的数据(好比:数字1,字符串‘hello lvya’,布尔值false等)和引用类型
: 保存的是地址值,这个地址值去指向某个对象。
toString()
和valueOf()
都是在Object.prototype
里面定义.
toString()
表示的含义是把这个对象表示成字符串形式, 而且返回这个字符串形式. 首先,在Object.prototype
中它对toString()方法的默认实现是"[object Object]"。 验证一下:
var p={};
console.log(p.toString()); //[object Object] 去Object.prototype的去找(输出他的默认实现)
function Person(){
}
var p1=new Person();
console.log(p1.toString()); //[object Object] 去Object.prototype的去找(输出他的默认实现)
复制代码
再看一下能够在本身的对象或者原型上对 toString() 进行覆写(重写, override)。这时访问这个对象的toString()方法时,就会沿着原型链上查找,恰好在自身对象上就找到了toString(),这个时候就再也不去找原型链上的顶端Object.prototype
的默认的toString()啦,便实现了对象的toString()的重写。 验证一下:
var p = {
toString: function (){
return "100";
}
};
//100 这个时候就会在首先在P对象上找toString()方法,这个时候就是对toString方法的重写
console.log(p.toString());
复制代码
再举一个重写的栗子:
var date = new Date();
console.log(date.toString());
//Fri Jan 18 2019 21:13:44 GMT+0800 (中国标准时间)
/*从输出结果可知,Date这个构造函数的原型实际上是有toString()方法的, 说明JS引擎已经在Date原型对象中重写了toString()方法, 故不会在Object.prototype中找*/
console.log(Date.prototype); //发现确实有toString()方法
var n = new Number(1);
console.log(n.toString()); //1(字符串)
/* 同理:这就是说明他们在js引擎内置的包装对象,说白了,就是内部已经给Number对象上重写了 toString()方法。这个方法恰好就是将数字转为字符串*/
复制代码
valueOf()
应该返回这个对象表示的基本类型的值!在Object.prototype.valueOf
中找到, 默认返回的是this。当须要在对象上重写valueOf()
时,应该是返回一个基本数据类型的值。 先看一个默认返回的值的状况。(也就是说它是去这个对象的原型链的顶端Object.prototype.valueOf
找的valueOf
方法 )
function Person(){
}
var p1 = new Person();
console.log(p1.valueOf() == p1); //true
复制代码
对返回结果的说明:这个时候p1.valueOf
是在Object.prototype.valueOf
找到的,返回值默认this。此时this就是p1的这个对象。故结果返回true
。 如今看一下重写valueOf后的状况
var p = {
toString: function (){
return "100";
},
valueOf : function (){
return 1;
}
};
console.log(p.toString()); //100(字符串)
//还来不及去Object.prototype.valueOf 其自己就有了toString方法 故固然读自己对象的toString()方法
console.log(p.valueOf()); //1(number数据类型)
//同理,没去Object.prototype.valueOf找 而是找其自己的valueOf方法
复制代码
咱们再来验证JS引擎对那些内置对象有去重写toString()
和valueOf()
呢?
var n = new Number(100);
console.log(n.valueOf()); //100 (number类型)
var s = new String("abc");
console.log(s.valueOf()); //abc (string类型)
var regExp = /abc/gi;
console.log(regExp.valueOf() === regExp); //true
//说明这个时候正则对象上没有valueOf,是在Object.prototype.valueOf找的,返回this,this指的就是regExp正则对象。
复制代码
结论:在JS中, 只有基本类型中那几个包装类型进行了重写, 返回的是具体的基本类型的值, 其余的类型都没有重写,是去对象原型链的顶层Object.prototype.valueOf
去找的。
了解完valueOf()
和toSting()
方法后,其实他们就是对象与基本数据类型的比较的基础。咱们数据类型,分为基本数据类型和对象类型两种,故在数据类型比较中,只会有三种状况:
基本数据类型间的比较
规则:若是类型相同,则直接比较; 若是类型不一样, 都去转成
number
类型再去比较 三个特殊点:1.undefined
==null
2.0
和undefined
,0
和null
都不等 3. 若是有两个NaN
参与比较,则老是不等的。
总结:都是基本数据类型,但当类型不一样时,转为number类型的规律以下:
基本类型中非number类型 | 转为number类型 |
---|---|
undefined ‘12a’ ‘abc’ ‘\’ |
Nan |
'' ' ' '\t' '0' null false |
0 |
true ‘1’ |
1 |
‘12’ |
12 |
咱们来看看ECMA官方文档对转number类型的说明:
另外 再补充一点,在JS世界里,只有五种转Boolean类型是false
的:
0
Nan
undefined
null
""
false
。其余的转Boolean值都是
true
。 咱们再来看看ECMA官方文档对转Boolean类型的说明:
因此,从这里咱们就能够发现其实原文的ECMA官方文档就是很棒的学习资料,已经帮你整理的很完备了。多去翻翻这些官方文档的资料颇有帮助。
例子1:属于基本类型间的比较,并且都是基本类型中的number类型,相同类型直接比较。
var a=1;
var b=1;
console.log(a == b); //true
console.log("0" == ""); //false
//都是相同的string类型,不用转,直接用字符串比较
复制代码
例子2:属于基本类型间的比较,可是其具体的类型不一样,须要转为number
类型再去比较。
console.log(true == "true"); //false 相应转为number类型去比较:1与Nan比较
console.log(0 == "0"); //true 相应转为number类型去比较:0与0比较
console.log(0 == ""); //true 相应转为number类型去比较:0与0比较
console.log(undefined == null); //true Nan与0比较??特殊
复制代码
例子3:属于三大特殊点
console.log(undefined == null); //true
console.log(undefined == 0); //false
console.log(null == 0); //false
console.log(Nan == Nan); //false
复制代码
对象类型间的比较
对象间的比较中
===
(严格相等:值和类型都相等) 和==
彻底同样。 规则:其实比较是否是同一个对象,比的就是他们的地址值是否同样。
例子1:对象类型间的比较
console.log({} === {}); //false 地址值不一样
console.log(new Number(1) == new Number(1)); //false 地址值不一样
复制代码
基本类型与对象类型间的比较
重点:这就是为啥以前引入
valueOf
和toString()
的道理。 规则:把对象转成基本类型的数据以后再比 ?如何把对象转换成基本类型:1. 先调用这个对象(注意是对象)的valueOf()
方法, 若是这个方法返回的是一个基本类型的值, 则用这个基本类型去参与比较。 2. 若是valueOf()
返回的不是基本类型, 则去调用toString()
而后用返回的字符串去参与比较。这个时候就是字符串与那个基本类型的比较,问题从而转为了基本类型间的比较。
例子1:
var p = {};
console.log(p.valueOf()); //{}
console.log(p == "[object Object]"); //true
复制代码
解释:首先明确是对象与基本类型中的字符串比较;按照规则,先把对象调用其valueOf()
,根据上节知识可知,返回的是this,也就是当前对象{}。不是基本数据类型,故再调用其toString()
,返回"[object Object]"
,从而进行基本数据类型间的比较,根据规则,类型相同都是字符串,直接比较,故相等。
例子2:
var p1 = {
valueOf : function (){
return 'abc';
},
toString : function (){
return {};
}
}
console.log(p1 == "abc"); //true
复制代码
解释:首先明确是对象与基本类型中的字符串比较;按照规则,先把对象调用其valueOf()
,根据上节知识可知,p有重写 valueOf
,故直接输出字符串'abc'
,它属于基本数据类型,故再也不调用其toString()
。进而进行基本数据类型间的比较,根据规则,类型相同都是字符串'abc'
,直接比较,故相等。
案例1: 基本数据类型的判断
typeof
返回数据类型的字符串(小写)表达
var a;
console.log(a, typeof a, typeof a === 'undefined', a === undefined) // undefined 'undefined' true true
console.log(undefined === 'undefined'); //false(转为number实则是Nan与0的比较)
a = 4;
console.log(typeof a === 'number'); //true
a = 'lvya';
console.log(typeof a === 'string'); //true
a = true;
console.log(typeof a === 'boolean'); //true
a = null;
console.log(typeof a, a === null); // 'object' true
复制代码
案例2: 对象类型的判断
var b1 = {
b2: [1, 'abc', console.log],
b3: function () {
console.log('b3');
return function () {
return 'ya Lv'
}
}
};
console.log(b1 instanceof Object, b1 instanceof Array); // true false
console.log(b1.b2 instanceof Array, b1.b2 instanceof Object) ;// true true
console.log(b1.b3 instanceof Function, b1.b3 instanceof Object); // true true
console.log(typeof b1.b2); // 'object'
console.log(typeof b1.b3 === 'function');// true
console.log(typeof b1.b2[2] === 'function'); // true
b1.b2[2](4); //4
console.log(b1.b3()()); //ya Lv
复制代码
instanceof
通常测对象类型,那它去测基本数据类型会出现怎样的奇妙火花呢?一块儿来验证一下。instanceOf
内部的实现原理能够直接看3.2.3节。
//1并非Number类型的实例
console.log(1 instanceof Number); //false
//new Number(1)的确是Number类型的实例
console.log(new Number(1) instanceof Number); //true
复制代码
面试3: 考察typeOf
检测数据类型
用
typeof
来测试有如下七种输出结果:'number'
'string'
'boolean'
'object'
'function'
'symol'
'undefined'
。注意都是字符串表达方式。
console.log(typeof "ab"); // string
console.log(String("ab")); //'ab' 能够知道String("ab")就是var s='ab'的含义
console.log(typeof String("ab")); // string
console.log(typeof new String("ab")); // object
console.log(typeof /a/gi); // object
console.log(typeof [0,'abc']); // object
console.log(typeof function (){}); //function
var f = new Function("console.log('abc')");
f(); //'abc' 能够知道f就是一个函数
console.log(typeof f); //function
console.log(typeof new Function("var a = 10")); //function
复制代码
面试4: 考察+
的运用
JS加号有两种用法: Case 1:数学上的加法(只要没有字符串参与运算就必定是数学上的数字): Case 2:字符串链接符(只要有一个是字符串,那就是字符串连接)
console.log(1 + "2" + "2"); // 122
console.log(1 + +"2" + "2"); // 32 (这里+'2'前面的加号是强转为number的意思)
console.log(1 + -"1" + "2"); // 02
console.log(+"1" + "1" + "2"); // 112
console.log( "A" - "B" + "2"); // NaN2
console.log( "A" - "B" + 2); // NaN
复制代码
面试5: 考察valueOf
和toString
console.log([] == ![]); //true
复制代码
说明:首先左边是[]
,右边是![]
这个是一个总体,由1.1.6节知识可知,世界上只有五种转Boolean值得是false
,其余都是true
。故右边这个![]
总体结果是false
。综上,明确这是对象与基本类型(布尔值)的比较。 而后,就是将对象先调用valueOf
后调用toString
的规则去判断,由1.1.6节可知,左边是对象,首先用valueOf
返回的是一个数组对象(注意若是是{}
。valueOf()
就是返回this
,此时this
是{}
!) 而后再调用toString
返回一个空的字符串,由于数组转字符串,就是去掉左右“中括号”,把值和逗号转为字符串,看一下验证:
console.log([].valueOf()); //[]
console.log([].valueOf().toString()); //空的字符串
复制代码
故左边是一个空的字符串。右边是false
。又转为基本数据间的比较,两个不一样类型,则转为number
类型去比较。 空字符串转number
为0,false
转number
为0。故0==0
结果就是true
。
面试6: &&
||
的短路现象
&&
||
在js中一个特别灵活的用法。若是第一个能最终决定结果的,那么结果就是第一个值,不然就是第二个。这个在实际项目中使用也很常见。 与和或的优先级:"与" 高于 "或",也就是&&
优先级大于||
console.log(1 && 2 || 0); // 2
console.log((0 || 2 && 1)); //1 (注意,这里是先计算2 && 1,由于&&优先级高于||)
console.log(3 || 2 && 1); // 1 (注意,这里是先计算2 && 1,由于&&优先级高于||)
console.log(0 && 2 || 1); // 1
复制代码
面试7: 类型转换综合题
+当作数字相加,由于两边都没字符串,故都转number
var bar = true;
console.log(bar + 0); // 1
复制代码
var bar = true;
console.log(bar + true); // 2
复制代码
var bar = true;
console.log(bar + false); // 1
复制代码
var bar = true;
console.log(bar + undefined); // Nan
复制代码
var bar = true;
console.log(bar + null); // 1
复制代码
console.log(undefined + null); // Nan (Nan与任何运算结果都是Nan)
复制代码
+当作字符串链接,由于有一个为字符串
var bar = true;
console.log(bar + "xyz"); // truexyz
复制代码
隐含的类型转换
console.log([1, 2] + {}); //1,2[object Object]
复制代码
Array.prototype.valueOf = function () {
return this[0];
};
console.log([1, 2] + [2]); //3
/**重写了Array的valueOf方法,其重写后返回的是this[0], 由于在这是number类型1,故直接用。*/
复制代码
console.log([{}, 2] + [2]); // [object Object],22
/**重写了Array的valueOf方法,其重写后返回的是this[0], 由于在这是一个对象{},故从新在对这个数组对象([{},2])调用toString()返回‘[object Object],2’。 这里要注意当调用toString是整个对象,而非重写valueOf后返回来的对象。 +右边的[2]是调用了valueOf以后返回的number类型2,因此直接用, 由于左边是一个字符串,因此加号表明字符串拼接。返回最终结果[object Object],22 */
复制代码
存储在内存中特定信息的"东东",本质上是0101...的二进制
数据的特色:可传递, 可运算
var a = 3;
var b = a;
复制代码
这里体现的就是数据的传递性。变量a是基本数据类型,保存的值是基本数据类型number值为3。在栈内存中存储。这两个语句传递的是变量a吗?不是。传递的是数据3。实际上,是拿到变量a的内容数字3拷贝一份到b的内存空间中。
注意:无论在栈内存空间保存的基本数据类型仍是在堆内存中保存的对象类型,这些内存都有地址值。只是要不要用这个地址值的问题。对象的地址值通常会用到。因此不少人会误觉得只有对象才有地址值,这是错误的理解。
内存条通电后产生的可存储数据的空间(临时的),它是临时的,但处理数据快
硬盘的数据是永久的,但其处理数据慢
内存产生和死亡: 内存条(电路版) -> 通电 -> 产生内存空间 -> 存储数据 -> 处理数据 -> 断电 -> 内存空间和数据都消失
内存空间的分类:
栈空间: 全局变量和局部变量【空间比较小】
堆空间: 对象 (指的是对象(函数也是对象)自己在堆空间里,其自己在堆内存中。但函数名在栈空间里。)【空间比较大】
//obj这个变量在栈空间里 name是在堆空间里
function fn () {
var obj = {name: 'lvya'}
}
复制代码
一块小的内存包含2个方面的数据
内部存储的数据(内容数据)
地址值数据(只有一种状况读的是地址值数据,那就是将一个对象给一个变量时)
var obj = {name: 'lvya'} ;
var a = obj ;
console.log(obj.name) ;
复制代码
执行var obj = {name: 'Tom'}
是将右边的这个对象的地址值给变量obj,变量obj这个内存里面保存的就是这个对象的地址值。 而var a = obj
右边不是一个对象,是一个变量(引用类型的变量),把obj的内容拷贝给a,而恰好obj的存储的内容是一个对象的地址值。 执行console.log(obj.name)
读的是obj.name的内容值。 总结:何时读的是地址值?只有把一个对象赋值给一个变量时才会读取这个对象在内存块中的地址值数据。上述三条语句只有var obj = {name: 'Tom'}
才是属于读地址值的状况。
问题一:var a = xxx, a内存中到底保存的是什么?
须要分类讨论:
当xxx是基本数据, 保存的就是这个数据
var a = 3;
//3是基本数据类型,变量a保存的就是3.
复制代码
当xxx是对象, 保存的是对象的地址值
a = function () {
}
//函数是对象,那么a保存的就是这个函数对象的地址值。
复制代码
当xxx是一个变量, 保存的xxx的内存内容(这个内容多是基本数据, 也多是地址值)
var b = 'abc'
a = b
//b是一个变量,而b自己内存中的内容是一个基本数据类型。
//因此,a也是保存这个基本数据类型'abc'
复制代码
b = {}
a = b
//b是一个变量,而b自己内存中的内容是一个对象的地址值。
//因此,a也是保存这个对象的地址值'0x123'
复制代码
问题二:关于引用变量赋值问题?
2个引用变量指向同一个对象, 经过一个变量修改对象内部数据, 另外一个变量看到的是修改以后的数据
var obj1 = {name: 'Tom'}
var obj2 = obj1
obj2.name = 'Git'
console.log(obj1.name) // 'Git'
function fn (obj) {
obj.name = 'A'
}
fn(obj1)
console.log(obj2.name) //A
复制代码
执行var obj2 = obj1
obj1
是一个变量,而非对象。故把obj1的内容拷贝给obj2,只是恰好这个内容是一个对象的地址值。这个时候,obj1
obj2
这两个引用变量指向同一个对象{name: 'Tom'}
。 经过其中一个变量obj2
修改对象内部的数据。 obj2.name = 'Git'
那么这个时候,另一个对象看到的是修改后的结果。固然,后面的对fn(obj1)
,也是同样的操做,涉及到实参和形参的赋值。
2个引用变量指向同一个对象, 让其中一个引用变量指向另外一个对象, 另外一引用变量依然指向前一个对象。
var a = {age: 12};
var b = a;
a = {name: 'BOB', age: 13};
b.age = 14;
console.log(a.name, a.age,b.age);// 'BOB' 13 14
function fn2 (obj) {
obj = {age: 100}
console.log(obj.age); //100
}
fn2(a); //函数执行完后会释放其局部变量
console.log(a.age,b.age) //13 14
console.log(obj.age); //报错 obj is not defined
复制代码
一开始两个引用变量a
b
都指向同一个对象,然后执行a = {name: 'BOB', age: 13};
语句,就是让a
指向另外一个对象{name: 'BOB', age: 13}
,a
中的内容的地址值变化了。而b
仍是指向以前a
的那个对象{age: 12}
。 这个例子要好好理解,看图解:未执行fn2函数以前和执行fn2函数后
问题三:在JS调用函数时传递变量参数时,是值传递仍是引用传递?
理解1: 都是值(基本类型的值/对象的地址值)传递
理解2: 多是值传递, 也多是引用传递(这个时候引用传递理解为对象的地址值)
var a = 3
function fn (a) {
a = a +1
}
fn(a)
console.log(a) //3
复制代码
fn(a)
中的a
是一个实参。function fn (a)
中的a
是一个形参。 var a = 3
中的a
是一个全局变量,其内存中存储的内容是基本数据类型3。实参与形参的传递是把3传递(拷贝)给形参中的a的内存中的内容。传递的不是a!而是3。而后,执行完以后,函数里面的局部变量a
被释放,当输出a
值时,确定去读取全局变量的a
。传递的是值3。
function fn2 (obj) {
console.log(obj.name) //'Tom'
}
var obj = {name: 'Tom'}
fn2(obj)
复制代码
fn2(obj)
中的实参obj
(引用变量)把其内容(恰好是地址值)传递给形参中的内容。而不是指把{name: 'Tom'}
整个对象赋值给形参obj
。是把地址值拷贝给形参obj
。也就是实参obj
形参obj
这两个引用变量的内容同样(地址值同样)。传递的是地址值。
问题四:JS引擎如何管理内存?
内存生命周期 分配小内存空间, 获得它的使用权 存储数据, 能够反复进行操做 释放小内存空间
释放内存 局部变量:函数执行完自动释放(全局变量不会释放内存空间) 对象:成为垃圾对象==>垃圾回收器回收(会有短暂间隔)
var a = 3
var obj = {}
//这个时候有三块小的内存空间:第一块 var a = 3 第二块 var obj 第三块 {} 在堆内存中
obj = undefined
//无论obj = null/undefined 这个时候内存空间还有两块。没有用到的{}会有垃圾回收器回收。
function fn () {
var b = {}
//b局部变量 整个局部变量的生命周期是在函数开始执行到结束。
}
fn() // 局部变量b是自动释放, b所指向的对象是在后面的某个时刻由垃圾回收器回收
复制代码
这一节主要是对对象的基本理解和使用做出阐述,一些基本的问题笔者会简单地在Part 1这部分罗列出来。具体的深刻问题在Part 2中深刻探讨。那么在了解对象的概念时,思想很重要,那就是对象是如何产生的?对象内部有啥须要关注?至于对象如何产出,有new出来,字面量定义出来的,函数return出来的等等状况。至于内部有啥呢,主要关注就是属性和方法这两个东西。
什么是对象?
为何要用对象? 统一管理多个数据。若是不这么作,那么就要引入不少的变量。 好比我如今要创建一个对象Person
,里面有name
age
gender
等等,我能够用一个对象去创建数据容器,就不须要单独设置不少变量了。方便管理。
对象的分类?
Math
/String
/Function
/Number
/Data
console.log()
document.write()
对象的组成?
在了解完对象以后,咱们知道每一个对象会去封装一些数据,用这个对象去映射某个事物。那么这些数据就是由属性来组成的,如今咱们看看属性的一些相关知识。
属性组成?
属性名 : 字符串(标识),本质上是字符串。本质上属性名是加引号的,也就是字符串。但通常实际操做都不加。
属性值 : 任意类型(因此会有方法是特别的属性这一说法。)
属性名本质上是字符串类型,见下例:
var obj={
'name':'猪八戒';
'gender':'男';
}
复制代码
上例子通常咱们不会特地这样去将属性名打上双引号,咱们通常习惯这样写:
var obj={
name:'猪八戒';
gender:'男';
}
复制代码
再看一道对象属性名知识点的面试题:
var a = {},
b = {key: 'b'},
c = {key: 'c'};
a[b] = 123; // a["[object Object]"] = 123
a[c] = 456; // a["[object Object]"] =456
console.log(a[b]); //输出456 求a["[object Object]"]=?
复制代码
上例解释:属性名本质上是字符串。ES6以前对象的属性名只能是字符串, 不能是其余类型的数据! 若是你传入的是其余类型的数据做为属性名, 则会把其余类型的数据转换成字符串,再作属性名。如果对象,那么就调用toString()
,ES6 属性名能够是Symbol
类型。 再看一个例子:
var a = {
"0" : "A",
"1" : "B",
length : 2
};
for(var i = 0; i < a.length; i++){//a是对象,a.length是读取对象的属性,为2.
console.log(a[i]);
}
//会输出A B
复制代码
再看一个例子:
var a = {};
a[[10,20]] = 2000;
//首先把握好a是对象,a[]就是使用对象读其属性的语法,而不是数组。把a[]中[10,20]本质上是字符串,因此要转啊。
//[10,20]转字符串就是对象转字符串,调用toString(),变成“10,20”。这个转的字符串就是属性名。
console.log(a); // 输出 {10,20: 2000}
复制代码
属性的分类?
数组和函数是特别的对象?
如何访问对象内部的数据 ?
.属性名
: 编码简单, 有时不能用。['属性名']
:编码麻烦, 能通用。var p = {
name: 'Tom',
age: 12,
setName: function (name) {
this.name = name
},
setAge: function (age) {
this.age = age
}
};
p.setName('Bob') //用.属性名的方式
p['setAge'](23) //用['属性名']语法
console.log(p.name, p['age']) //Bob 23
复制代码
何时必须使用['属性名']
的方式?
var p = {};
//1. 给p对象添加一个属性: content type: text/json
// p.content-type = 'text/json' //不能用
p['content-type'] = 'text/json'
console.log(p['content-type'])
//2. 属性名不肯定,用变量去存储这个值。
var propName = 'myAge'
var value = 18
// p.propName = value //不能用
p[propName] = value //propName表明着的就是一个变量
console.log(p[propName]) //18
复制代码
函数对象(Function Object)是什么呢?
其实在JavaScript中笔者认为最复杂的数据类型,不是对象,而是函数。为何函数是最复杂的数据类型呢?由于函数能够是对象,它自己就会有对象的复杂度。函数又能够执行,它有不少的执行调用的方式(这也决定了函数中this
是谁的问题),因此他又有函数执行的复杂度。这一小节咱们就简单来讲说函数的基本知识。在Part3会去更深刻去介绍JS中的函数。
什么是函数?
为何要用函数?
提升复用性(封装代码)
便于阅读交流
function showInfo (age) {
if(age<18) {
console.log('未成年, 再等等!')
} else if(age>60) {
console.log('算了吧!')
} else {
console.log('恰好!')
}
}
//若是不用函数作,也能够,但要把中间的代码书写不少遍。
//而函数就是抽象出共同的东西,把这些执行过程封装起来,给你们一块儿用。
showInfo(17) //未成年, 再等等!
showInfo(20) //恰好!
showInfo(65) //算了吧!
复制代码
如何定义函数 ?
函数声明
表达式
建立函数对象 var fun = new Function( ) ;
通常不使用
function fn1 () { //函数声明
console.log('fn1()')
}
var fn2 = function () { //表达式
console.log('fn2()')
}
fn1();
fn2();
复制代码
如何调用(执行)函数?
test()
: 直接调用
obj.test()
: 经过对象去调用
new test()
: new调用
test.call/apply(obj)
: 临时让test成为obj对象的方法进行调用
var obj = {} //一个对象
function test2 () { //一个函数
this.xxx = 'lvya'
}
// obj.test2() 不能直接, 根本obj对象中就没有这样的函数(方法)
test2.call(obj) // 至关于obj.test2() , 可让一个函数成为指定任意对象的方法进行调用
console.log(obj.xxx) //lvya
//这个借调是JS有的,其余语言作不到。借调就是假设一个对象中没有一个方法,
//那么就可让这个方法成为想要调用这个方法的对象去使用的方式。
//也就是一个函数能够成为指定任意对象的方法进行调用 。
复制代码
函数也是对象
prototype
call()
/apply()
函数的3种不一样角色
对象.
调用内部的属性/方法this是什么?
如何肯定this的值?
test()
: window
p.test()
: p
new test()
: 新建立的对象
p.call(obj)
: obj
回调函数: 看背后是经过谁来调用的: window/其它
<script type="text/javascript">
function Person(color) {
console.log(this)
this.color = color;
this.getColor = function () {
console.log(this)
return this.color;
};
this.setColor = function (color) {
console.log(this)
this.color = color;
};
}
Person("red"); //this是谁? window
var p = new Person("yello"); //this是谁? p(Person)
p.getColor(); //this是谁? p (Person)
var obj = {};
p.setColor.call(obj, "black"); //this是谁? obj (Object)
var test = p.setColor;
test(); //this是谁? window
function fun1() {
function fun2() {
console.log(this);
}
fun2();
}
fun1(); //this是谁? window
</script>
复制代码
匿名函数自调用:
(function(w, obj){
//实现代码
})(window, obj)
复制代码
;(function () { //匿名函数自调用
var a = 1
function test () {
console.log(++a)
}
window.$ = function () { // 向外暴露一个全局函数
return {
test: test
}
}
})()
$().test() //需明白 1. $是一个函数 2. $执行后返回的是一个对象 3.而后对象.方法()执行函数。
复制代码
回调函数的理解
回调函数类型 | this是指向谁? |
---|---|
DOM事件回调函数 | 发生事件的DOM元素 |
定时器回调函数 | Window |
ajax请求回调函数 | Window |
生命周期回调函数 | 组件对象 |
函数中的arguments 在调用函数时,浏览器每次都会传递两个隐含的参数:
this
arguments
(类数组对象)。这里的实参是重点,就是执行函数时实际传入的参数的集合。function foo() {
console.log(arguments); //Arguments(3)返回一个带实参数据的类数组
console.log(arguments.length); //3 类数组的长度
console.log(arguments[0]); //ya LV 能够不传形参,能够访问到实参
console.log(arguments.callee); // ƒ foo() {...} 返回对应当前正在执行函数的对象
}
foo('ya LV',18,'male');
复制代码
arguments妙用1:利用arguments实现方法的重载
a.借用arguments.length属性来实现
function add() {
var len = arguments.length,
sum = 0;
for(;len--;){
sum += arguments[len];
}
return sum;
}
console.log( add(1,2,3) ); //6
console.log( add(1,3) ); //4
console.log( add(1,2,3,5,6,2,7) ); //26
复制代码
b.借用prototype属性来实现
function add() {
return Array.prototype.reduce.call(arguments, function(n1, n2) {
return n1 + n2;
});
};
add(1,2,3,6,8); //20
//三个经常使用的数组的高阶函数:map(映射)filter(过滤)reduce(概括)
//能够参见ES6函数新增特性之箭头函数进一步优化
复制代码
arguments妙用2.利用arguments.callee
实现递归
先来看看以前咱们是怎么实现递归的,这是一个计算阶乘的函数:
function factorial(num) {
if(num<=1) {
return 1;
}else {
return num * factorial(num-1);
}
}
复制代码
可是当这个函数变成了一个匿名函数时,咱们就能够利用callee
来递归这个函数。
function factorial(num) {
if(num<=1) {
return 1; //若是没有这个判断,就会内存溢出
}else {
return num * arguments.callee(num-1);
}
}
console.log(factorial(5)); //120
复制代码
js一条语句的后面能够不加分号,相似“能够加分号可是你们都不加” 的语言就有:Go
, Scala
, Ruby
, Python
, Swift
, Groovy
...
是否加分号是编码风格问题, 没有应该不该该,只有你本身喜欢不喜欢
在下面2种状况下不加分号会有问题
小括号开头的前一条语句
var a = 3
;(function () {
})()
//若是不加分号就会这么错误解析:
// var a = 3(function () {
// })();
复制代码
中方括号开头的前一条语句
var b = 4
;[1, 3].forEach(function () {
})
// 若是不加分号就会这么错误解析:
// var b = 4[3].forEach(function () {
// })
复制代码
解决办法: 在行首加分号
强有力的例子: Vue.js
库。Vue.js
的代码所有不带分号。
有一个工具全自动帮你批量添加或者删除分号:工具地址
像二进制,八进制,十进制,十六进制这些概念在JavaScript中不多被体现出来,但是笔者以为这个是码农的素养,因此我以为有必要再去搞懂。另一个就是原码反码补码的概念。好比在计算机硬件电路中有加法器,全部的运算都会转为加法运算,减法就是用加法来实现。因此才引出原码反码补码的概念去解决这一问题。
那么笔者如今着重讲一下位运算符操做和移位操做。js中位运算符有四种:按位取反(~
)、按位与(&
)、按位或(|
)、按位异或(^
)。移位操做有四种:带符号向右移动(>>
)、无符号向右移动(>>>
)、带符号向左移动(<<
)、无符号向左移动(<<<
).
示例1:如何快速判断一个数是否是奇数?
那么,取余是你先想到的,那么还有其余方法吗?就是用位运算符去解答。先思考奇数3(二进制是11),偶数4(二进制是100),可知偶数的最低位为0,奇数的最低位为1,那么咱们只要经过某种方法获得一个数的二进制的最低位,判断它是否是为1,是1那这个数就是奇数。
如今的问题就变成了,怎么获得一个数的二进制最低位呢?那就是用按位与1(& 1
)去作。假设一个数的二进制为1111 1111 那么只要按位与1(1的二进制为0000 0001)是否是前面一排“与0”都变成0了,只剩最低位了,这样目标数与1的按位与运算的结果就是等价于目标数的二进制最低位。
var num = 57 ;
if(num & 1){
console.log(num + "是奇数"); //57是奇数
}else{
console.log(num + "是偶数");
}
复制代码
示例2:怎么交换两个number
类型的变量值?
新增一个变量来存储这种方式是你先想到的,那么另一种就是经过按位异或操做去交换变量。
异或就是不一样的为true(1),相同的为false(0)。
10^10=0 由于1010 ^ 1010 = 0000
11^0=11 由于1011 ^ 0000=1011
因此获得两个结论:
第一,两个相同的number数异或为0;第二,任何number数与0异或是其自己。
var a = 10;
var b = 20;
a = a ^ b; //a=10 ^20
b = a ^ b; //b=10 ^20^20 =10 ^(20^20)=10^0=10
a = a ^ b; //a=10 ^20^ 10 =(10^10)^20=0^20=20
console.log(a, b); //20 10 -交换变量成功-
//但这种方法只适用于number数据类型。
//另外能够用ES6中的数组解构。
复制代码
示例3:如何计算出一个数字某个二进制位?
在回答这个问题前,咱们先总结出一些结论供咱们使用。移位都是当作32位来移动的,但咱们这里就简单操做,用8位来模拟。
先看带符号向右
移位:
10 >>
1 翻译题目:10带符号向右移动一位是几?
0000 1010 >>
1
0000 0101 这个结果就是移位后的结果。咱们能够知道0101就是十进制的5.
带符号向右移动就是总体向右移动一位,高位用符号位去补。正数用0补,负数用1补。
咱们能够看出结论,带符号向右移动其实就是往右移动一位,至关于除以2.
如今再来看看带符号向左移位:
10 <<
2 翻译题目: 10带符号向左移动2位是几?
0000 1010 <<
2
0010 1000 低位用0补。这个 0010 1000 就是数就是40.
咱们能够看出结论,带符号向左移动其实就是往左移动一位,至关于乘以2。移动2位便是乘4。
如今回归题目,假设我要知道10(1010)的倒数第三位的0这个进制位。
首先往右移动两位变成0010 , 而后进行 ‘&1
’ 操做 , 0010 &
0001 =0000 =0 ,这个0就是10的二进制位的倒数第三位。因此是经过:(10 >>
2 &
1)的方式获得10的倒数第三位的进制位。
示例4:如何计算出2的6次方最快算法?
2B程序猿会用2 * 2 * 2 * 2 * 2 * 2
的方法吧。码农可能会用for
循环去作或者用Math.pow(2,6)
去写。
可是这些都不是最快的。咱们来看看高级工程师会怎么写,哈哈。咱们刚刚获得过2个结论,其中一个就是带符号向左移位其实就是往左移动一位,至关于乘以2。 移动2位,就是乘4。"左乘右除"
。那么如今我是否是能够对 1 移动6位 不就能够了吗?因此就一行代码:1 <<
6 。
由汇编知识咱们知道,移位是最底层的计算。能够彻底用加法器实现。而Math.pow(2,6)
其实会有不少的汇编指令才能够实现这一条代码。但1 <<
6 只须要一条,因此,性能是很好的。
内存溢出:
一种程序运行出现的错误
当程序运行须要的内存超过了剩余的内存时, 就出抛出内存溢出的错误。
// 1. 内存溢出
var obj = {}
for (var i = 0; i < 10000; i++) {
obj[i] = new Array(10000000)
console.log('-----')
}
//直接崩掉了,须要的内存大于目前空闲的内存,直接报错误:内存不足。
//就如一个水杯,水倒满了就溢出,这就是内存溢出。
复制代码
内存泄露:
占用的内存没有及时释放,这时程序仍是能够正常运行的
内存泄露积累多了就容易致使内存溢出
常见的内存泄露:
意外的全局变量
// 在乎外的全局变量--在ES5的严格模式下就会报错。
function fn() {
a = new Array(10000000)
console.log(a)
}
fn()
//a就是意外的全局变量,一直会占着内存,关键它仍是指向一个数组很是大的对象。这块内存就一直占着。
复制代码
没有及时清理的计时器或回调函数
// 没有及时清理的计时器或回调函数
var intervalId = setInterval(function () { //启动循环定时器后不清理
console.log('----')
}, 1000)
// clearInterval(intervalId)
复制代码
闭包
// 闭包
function fn1() {
var a = 4
function fn2() {
console.log(++a)
}
return fn2
}
var f = fn1()
f()
//f指向的fn2函数对象一直都在,设f指向空对象,进而让fn2成为垃圾对象,进而去回收闭包。
// f = null
复制代码
函数节流:让一段程序在规定的时间内只执行一次
<script type="text/javascript">
window.onload = function () {
// 函数节流: 让一段程序在规定的时间内只执行一次
let flag = false;
document.getElementsByTagName('body')[0].onscroll = function () {
if(flag){
return;
}
flag = true; //当以前为FALSE的时候进来,我在2s内才会有定时器注册。
setTimeout(function () {
console.log('滚动过程当中,2s只会注册定时器一次');
flag = false; //一次后,为第二次作准备。只要是TRUE,我就进不来注册定时器的逻辑
}, 2000)
}
}
</script>
复制代码
函数防抖: 让某一段程序在指定的事件以后触发
<script type="text/javascript">
//场景:让滚动完以后2s触发一次。
window.onload = function () {
// 函数防抖: 让某一段程序在指定的事件以后触发
let timeoutId = null;
document.getElementsByTagName('body')[0].onscroll = function () {
timeoutId && clearTimeout(timeoutId); //当第一次来的时候为null,不须要清除定时器。
timeoutId = setTimeout(function () {
console.log('xxx');
}, 2000) //在这个滚动过程当中的最后一次才注册成功了,其余的定时器都注册完后立刻清除。
}
}
</script>
复制代码
此文档为吕涯原创,可任意转载,但请保留原连接,标明出处。 文章只在CSDN和掘金第一时间发布: CSDN主页:https://blog.csdn.net/LY_code 掘金主页:https://juejin.im/user/5b220d93e51d4558e03cb948 如有错误,及时提出,一块儿学习,共同进步。谢谢。 😝😝😝