隐式转换
隐式转换 数据类型 +-巧用 a==b a===b(严格等于)包装对象
原始类型是没有对象属性的,但是类似a.t或者a.length等操作,会转换成一个临时对象,但是当返回后,临时对象被销毁,所以alert(a.t)为undefined
类似上面类型检测
typeoftypeof返回字符串,判断基本类型,函数对象非常方便,对其他对象不怎方便
instanceof Object.prototype.toStringinstanceof判断对象类型,基于原型链判断;typeof返回字符串,通常用于原始类型以及函数对象,如判断一个东西是不是原始类型或者函数,用typeof比较合适。
var a=b=1; a被创建为局部/全局(看位置)变量,b被创建为全局变量
函数声明与函数表达式区别:函数前置,函数声明会被预先处理,可在其前面调用函数,函数表达式则不行。
obj.hasOwnProperty('z'); //true 检测obj上是否存在z属性(判断自身属性是否存在)
!注意:用for in遍历属性有可能会把原型链上的属性也遍历出来,但是顺序是不确定的
hasOwnProperty() //判断对象是否存在属性,同时可用来过滤掉原型链上的属性(巧用) propertyIsEnumerable() //判断对象是否可枚举
!!注意:defineProperty定义属性默认等于false;巧用&&可以用来判断属性是否存在;!=一般等于空,undefined等于null
sort按字母大小排序
arr.sort(function(a,b){
return a - b
});
slice():索引切片,左闭右开区间,负数索引表示从后往前,对原数组进行修改
arr.forEach(function(a,index,n){
})
a:值,index:索引,n:true/false
reduce() 方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。
reduceRight()方法的功能和reduce()功能是一样的,不同的是reduceRight()从数组的末尾向前将数组中的数组项做累加。
第一个参数:查找的元素;第二个参数:开始查找的位置
函数:
函数声明可声明前置,函数表达式不可以
不常见,a、b:形参,第三个:字符串代码,存在安全隐患
this
闭包:
闭包的作用:结合实际案例:
https://blog.csdn.net/u011086209/article/details/88858277
作用域:
在javascript中,是没有块级作用域这种说法的
匿名函数(自执行)
匿名函数顾名思义指的是没有名字的函数,在实际开发中使用的频率非常高!也是学好JS的重点。
匿名函数:没有实际名字的函数。
首先我们声明一个普通函数:
//声明一个普通函数,函数的名字叫fn
function fn(){
console.log("张培跃");
}
然后将函数的名字去掉即是匿名函数:
//匿名函数,咦,运行时,你会发现报错啦!
function (){
console.log("张培跃");
}
到此,你会发现单独运行一个匿名函数,由于不符合语法要求,报错啦!解决方法只需要给匿名函数包裹一个括号即可:
//匿名函数在其它应用场景括号可以省略
(function (){
//由于没有执行该匿名函数,所以不会执行匿名函数体内的语句。
console.log("张培跃");
})
如果需要执行匿名函数,在匿名函数后面加上一个括号即可立即执行!
(function (){
//此时会输出张培跃
console.log("张培跃");
})()
倘若需要传值,直接将参数写到括号内即可:
(function (str){
//此时会输出张培跃好帅!
console.log("张培跃"+str);
})("好帅!")
匿名函数的应用场景
1、事件
<input type="button" value="点我啊!" id="sub">
<script>
//获得按钮元素
var sub=document.querySelector("#sub");
//给按钮增加点击事件。
sub.onclick=function(){
alert("当点击按钮时会执行到我哦!");
}
</script>
2、对象
var obj={
name:"张培跃",
age:18,
fn:function(){
return "我叫"+this.name+"今年"+this.age+"岁了!";
}
};
console.log(obj.fn());//我叫张培跃今年18岁了!
3、函数表达式
//将匿名函数赋值给变量fn。
var fn=function(){
return "我是一只小小小小留下,怎么飞也飞不高!"
}
//调用方式与调用普通函数一样
console.log(fn());//我是一只小小小小留下,怎么飞也飞不高!
4、回调函数
setInterval(function(){
console.log("我其实是一个回调函数,每次1秒钟会被执行一次");
},1000);
5、返回值
//将匿名函数作为返回值
function fn(){
//返回匿名函数
return function(){
return "张培跃";
}
}
//调用匿名函数
console.log(fn()());//张培跃
//或
var box=fn();
console.log(box());//张培跃
模仿块级作用域
块级作用域,有的地方称为私有作用域。JavaScript中是没有块级作用域的,例如:
if(1==1){//条件成立,执行if代码块语句。
var a=12;//a为全局变量
}
console.log(a);//12
for(var i=0;i<3;i++){
console.log(i);
}
console.log(i);//4
if(){}for(){}等没有自己的作用域。如果有,出了自己的作用域,声明的变量就会立即被销毁了。但是咱们可以通过匿名函数来模拟块级作用域:
(function(){
//这里是我们的块级作用域(私有作用域)
})();
尝试块级作用域:
function fn(){
(function(){
var la="啦啦啦!";
})();
console.log(la);//报错---la is not defined
}
fn();
匿名函数的作用:
1、通过匿名函数可以实现闭包,关于闭包在后面的文章中会重点讲解。在这里简单介绍一下:闭包是可以访问在函数作用域内定义的变量的函数。若要创建一个闭包,往往都需要用到匿名函数。
2、模拟块级作用域,减少全局变量。执行完匿名函数,存储在内存中相对应的变量会被销毁,从而节省内存。再者,在大型多人开发的项目中,使用块级作用域,会大大降低命名冲突的问题,从而避免产生灾难性的后果。自此开发者再也不必担心搞乱全局作用域了。
执行上下文:
OOP
Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__。
用于继承Person.prototype,避免对Person.prototype造成污染
bind函数没有prototype属性
hasOwnProperty() 方法会返回一个布尔值,指示对象自身属性中是否具有指定的属性(也就是,是否有指定的键)。
继承:
第一种不可以,会污染Person.prototype属性
js继承的6种方式
想要继承,就必须要提供个父类(继承谁,提供继承的属性)
一、原型链继承
重点:让新实例的原型等于父类的实例。
特点:1、实例可继承的属性有:实例的构造函数的属性,父类构造函数属性,父类原型的属性。(新实例不会继承父类实例的属性!)
缺点:1、新实例无法向父类构造函数传参。
2、继承单一。
3、所有新实例都会共享父类实例的属性。(原型上的属性是共享的,一个实例修改了原型属性,另一个实例的原型属性也会被修改!)
二、借用构造函数继承
重点:用.call()和.apply()将父类构造函数引入子类函数(在子类函数中做了父类函数的自执行(复制))
特点:1、只继承了父类构造函数的属性,没有继承父类原型的属性。
2、解决了原型链继承缺点1、2、3。
3、可以继承多个构造函数属性(call多个)。
4、在子实例中可向父实例传参。
缺点:1、只能继承父类构造函数的属性。
2、无法实现构造函数的复用。(每次用每次都要重新调用)
3、每个新实例都有父类构造函数的副本,臃肿。
三、组合继承(组合原型链继承和借用构造函数继承)(常用)
重点:结合了两种模式的优点,传参和复用
特点:1、可以继承父类原型上的属性,可以传参,可复用。
2、每个新实例引入的构造函数属性是私有的。
缺点:调用了两次父类构造函数(耗内存),子类的构造函数会代替原型上的那个父类构造函数。
四、原型式继承
重点:用一个函数包装一个对象,然后返回这个函数的调用,这个函数就变成了个可以随意增添属性的实例或对象。object.create()就是这个原理。
特点:类似于复制一个对象,用函数来包装。
缺点:1、所有实例都会继承原型上的属性。
2、无法实现复用。(新实例属性都是后面添加的)
五、寄生式继承
重点:就是给原型式继承外面套了个壳子。
优点:没有创建自定义类型,因为只是套了个壳子返回对象(这个),这个函数顺理成章就成了创建的新对象。
缺点:没用到原型,无法复用。
六、寄生组合式继承(常用)
寄生:在函数内返回对象然后调用
组合:1、函数的原型等于另一个实例。2、在函数中用apply或者call引入另一个构造函数,可传参
重点:修复了组合继承的问题
继承这些知识点与其说是对象的继承,更像是函数的功能用法,如何用函数做到复用,组合,这些和使用继承的思考是一样的。上述几个继承的方法都可以手动修复他们的缺点,但就是多了这个手动修复就变成了另一种继承模式。
这些继承模式的学习重点是学它们的思想,不然你会在coding书本上的例子的时候,会觉得明明可以直接继承为什么还要搞这么麻烦。就像原型式继承它用函数复制了内部对象的一个副本,这样不仅可以继承内部对象的属性,还能把函数(对象,来源内部对象的返回)随意调用,给它们添加属性,改个参数就可以改变原型对象,而这些新增的属性也不会相互影响。
Javascript是弱类型,不想java,没有绝对的机制去实现参数重载,因为函数不确定,传入个数可以任意。
正则表达式:
垃圾回收机制
垃圾回收
JavaScript 中的内存管理是自动执行的,而且是不可见的。我们创建基本类型、对象、函数……所有这些都需要内存。
当不再需要某样东西时会发生什么? JavaScript 引擎是如何发现并清理它?
可达性
JavaScript 中内存管理的主要概念是可达性。
简单地说,“可达性” 值就是那些以某种方式可访问或可用的值,它们被保证存储在内存中。
1. 有一组基本的固有可达值,由于显而易见的原因无法删除。例如:
本地函数的局部变量和参数
当前嵌套调用链上的其他函数的变量和参数
全局变量
还有一些其他的,内部的
这些值称为根。
2. 如果引用或引用链可以从根访问任何其他值,则认为该值是可访问的。
例如,如果局部变量中有对象,并且该对象具有引用另一个对象的属性,则该对象被视为可达性, 它引用的那些也是可以访问的,详细的例子如下。
JavaScript 引擎中有一个后台进程称为垃圾回收器,它监视所有对象,并删除那些不可访问的对象。
一个简单的例子
下面是最简单的例子:
这里箭头表示一个对象引用。全局变量“user”引用对象 {name:“John”} (为了简洁起见,我们将其命名为John)。John 的 “name” 属性存储一个基本类型,因此它被绘制在对象中。
如果 user 的值被覆盖,则引用丢失:
现在 John 变成不可达的状态,没有办法访问它,没有对它的引用。垃圾回收器将丢弃 John 数据并释放内存。
两个引用
现在让我们假设我们将引用从 user 复制到 admin:
现在如果我们做同样的事情:
该对象仍然可以通过 admin 全局变量访问,所以它在内存中。如果我们也覆盖admin,那么它可以被释放。
相互关联的对象
现在来看一个更复杂的例子, family 对象:
函数 marry 通过给两个对象彼此提供引用来“联姻”它们,并返回一个包含两个对象的新对象。
到目前为止,所有对象都是可访问的。
现在让我们删除两个引用:
仅仅删除这两个引用中的一个是不够的,因为所有对象仍然是可访问的。
但是如果我们把这两个都删除,那么我们可以看到 John 不再有传入的引用:
输出引用无关紧要。只有传入的对象才能使对象可访问,因此,John 现在是不可访问的,并将从内存中删除所有不可访问的数据。
无法访问的数据块
有可能整个相互连接的对象变得不可访问并从内存中删除。
源对象与上面的相同。然后:
这个例子说明了可达性的概念是多么重要。
很明显,John和Ann仍然链接在一起,都有传入的引用。但这还不够。
“family”对象已经从根上断开了链接,不再有对它的引用,因此下面的整个块变得不可到达,并将被删除。
内部算法
基本的垃圾回收算法称为“标记-清除”,定期执行以下“垃圾回收”步骤:
垃圾回收器获取根并“标记”(记住)它们。
然后它访问并“标记”所有来自它们的引用。
然后它访问标记的对象并标记它们的引用。所有被访问的对象都被记住,以便以后不再访问同一个对象两次。
以此类推,直到有未访问的引用(可以从根访问)为止。
除标记的对象外,所有对象都被删除。
例如,对象结构如下:
我们可以清楚地看到右边有一个“不可到达的块”。现在让我们看看“标记并清除”垃圾回收器如何处理它。
现在进程中不能访问的对象被认为是不可访问的,将被删除:
这就是垃圾收集的工作原理。JavaScript引擎应用了许多优化,使其运行得更快,并且不影响执行。
一些优化:
分代回收——对象分为两组:“新对象”和“旧对象”。许多对象出现,完成它们的工作并迅速结 ,它们很快就会被清理干净。那些活得足够久的对象,会变“老”,并且很少接受检查。
增量回收——如果有很多对象,并且我们试图一次遍历并标记整个对象集,那么可能会花费一些时间,并在执行中会有一定的延迟。因此,引擎试图将垃圾回收分解为多个部分。然后,各个部分分别执行。这需要额外的标记来跟踪变化,这样有很多微小的延迟,而不是很大的延迟。
空闲时间收集——垃圾回收器只在 CPU 空闲时运行,以减少对执行的可能影响。
面试怎么回答
1)问什么是垃圾
一般来说没有被引用的对象就是垃圾,就是要被清除, 有个例外如果几个对象引用形成一个环,互相引用,但根访问不到它们,这几个对象也是垃圾,也要被清除。
2)如何检垃圾
一种算法是 标记-清除 算法,另一种是引用计数(主要用在IE游览器)
call()、apply()
apply方法能劫持另外一个对象的方法,继承另外一个对象的属性
Function.apply(obj,args)方法能接收两个参数,第一个参数是在其中运行函数的作用域,另一个是参数数组。其中,第二个参数可以是数组,也可以是arguments对象
obj:这个对象将代替Function类里this对象
args:这个是数组,它将作为参数传给Function(args–>arguments)
学生类里没有给name和age属性赋值啊,为什么又存在这两个属性的值呢,这个就是apply的神奇之处。
分析:Person.apply(this,arguments);
this:代表的是student,表示将Person中的this指向student。
arguments:是一个数组,在例子中表示[‘A','10','一年级'];
通俗一点讲就是:用student去执行Person这个类里面的内容,在Person这个类里面存在this.name等之类的语句,这样就讲属性创建到了student对象里面.
apply()和call()的作用是一样的,他们的区别仅在于接收参数的方式不同,call()需要把参数一个个列出来。
call()的用法
在Student函数里面可以将apply中修改成如下:
3.什么情况下用apply(),什么情况下用call()
至于是使用apply()还是call(),取决于采取那种给函数传递参数的方式最方便。
如果打算直接传入arguments对象,或者是数组,并且参数列表相同则使用apply();否则,选择call()可能更合适。
例:apply示例里面传递了参数arguments,并且在调用Person的时候参数的列表是对应一致的,也就是Person和Student的参数列表前两位是一致的,就可以采用apply。
如果我的Person的参数列表是这样的(age,name)而Student的参数列表是(name,age,grade),这样的就可以用call来实现了Person.call(this,age,name,grade);
网友评论