基础知识深入
1、关于作用域的小问题
var a={age:15}
function fun(obj){
obj={age:13}
}
fun(a)
console.log(a,a.age)//{age: 15} 15
var b=3
function fun2(b){
b=b+1
}
fun2(b)
console.log(b) //3
解释:其实形参相当于是赋值,也就是相当于obj=a;这个时候,obj指向的是{age:15},然后在函数内部使得obj指向的是{age:13},然后函数结束以后局部变量被销毁。所以a的指向一直都没有改变,所以a依然是指向{age:15}
2、对象---什么时候必须使用['属性名']的方式?
1)属性名包含特殊字 符:- 空格
2)属性名不确定,下面属性名是myAge 赋值给一个变量
var propName="myAge"
var value=18
p.propName=value //不能用
p[propName]=value //可以
3、函数
①call的其他理解
var obj={}
funtion test(){
this.name="sjw"
this.age=18// 是改变this指向,也是给obj添加属性
this.setAge=function(){
this.age=22
}
}
这个时候不可以obj.name因为obj是空对象
test.call(obj)//改变了this的指向
console.log(obj.name)//sjw
obj.setAge()
console.log(obj.age)//22
并且console.log(obj)
{name:"sjw",age:18} 相当于是改变了obj的内容
除了理解为改变this的指向,其他理解为:让函数成为指定的任意对象的方法进行调用
②回调函数
1)什么函数才是回调函数:
1>你定义的
2>你没有调
3>执行了(在某个条件满足时)
2)一般的回调函数
1>定时器回调函数
2>dom事件回调函数
3>ajax请求回调函数
4>生命周期回调函数
③IIFE(立即执行函数表达式)
作用:隐藏实现,不会污染外部(全局)的命名空间
;(function (){
var a=3
console.log(a+3)//6
})()
var a=4
console.log(a) //4
-----------------------------------------------------------------------------
;(function (){
var b=1
function test(){
console.log(++b)
}
window.xx=function(){
return{
test:test //相当于向外暴露一个函数test
}
}
})()
obj=xx()
obj.test() //2
xx().test() //3
④函数中的this
1)this是什么?
任何函数本质上都是通过对象调用的,如果没有直接指定就是window
所有函数内部都有一个变量this
2)如何确定this的值
* test() //window
* p.test() //p
* new test() //新创建的对象
* p.call(obj) //obj
tips:小括号开头前一句和中方括号开头的前一句要加; 其他自由
JS高级
1、原型和原型链
1)函数的prototype
* 每个函数都有一个prototype属性,他默认指向一个object空对象(原型对象)
* 原型对象中有一个属性constructor,它指向函数对象
2)给原型对象添加属性(一般为方法)
* 作用:函数中所有实例对象自动拥有原型中的属性(方法)
3)显式原型和隐式原型
* 每个函数function都有一个显式原型propotype
* 每个实例对象都有一个隐式原型__proto__(都保存着地址值)
var fn=new Fn() //相当于this.__propo=Fn.propotype
此外,Fn.prototype.constructor=Fn 也就是空object里面还有一个属性,指向函数对象
4)原型链
* 访问一个对象属性时
* 现在自己身上找
* 没有在自己的原型对象上找
* 顺着__proto__原型链找 找到返回 找到Object上还没有的话,返回undefined
5)补充内容:
* 函数的显式原型指向对象默认是空Object实例对象(但Object不满足)
* 所有函数都是Function的实例(包括Function)
Function.__proto__===Function.prototype
* Objetc对象的原型对象是原型链尽头
Object.prototype.__proto__=null
6)原型链_属性设置
* 设置对象的属性值时:不查找原型链,若当前对象中没有此属性,直接添加此属性并设置
function fn(){
}
fn.prototype.a="xxx"
var fn1=new fn( )
console.log(fn1.a) // xxx
var fn2=new fn( )
fn2.a="yyy"
console.log(fn1.a) // xxx 没改变因为fn2加的在自己的对象上 而不在原型上
* 方法一般定义在原型上,属性一般定义在对象本身上
7)关于instanceof
函数Foo的原型对象又是空的Object的实例对象,因此也有__proto__指向Object( )函数的原型对象(实例对象的隐式原型等于构造函数的显式原型)
2、执行上下文和执行上下文栈
1)变量声明提升和函数提升
var a=3;
function fn( ){
console.log(a) // undefined
var a=4;
}
相当于:
var a=3;
function fn( ){
var a;
console.log(a) // undefined
a=4;
}
注意:没有赋值,只是把声明提升
-----------------------------------------------------------------------------------------------------------------
fn() //可以调用 函数提升
function fn( ){
var a;
console.log(a) // undefined
a=4;
}
注意:先执行变量提升,然后再执行函数提升
2)执行上下文栈....
3、作用域和作用域链
1)分类
* 全局作用域
* 局部作用域
* es6有了块作用域
2)作用:隔离变量,不同作用域下同名的变量不会冲突
3)n+1原则:n是你定义的函数,1是全局作用域
fn(10)
4)作用域链
*先在自己的作用域中找,没有的话在上一级的作用域中找
var a=1
function fn1( ){
var b=2
function fn2( ){
var c=3
console.log(c)
console.log(b)
console.log(a)
console.log(d)
}
fn2( )
}
fn1( ) //执行后:3 2 1 报错
答案:10 原因是作用域本身已经形成了,所以你虽然调用是嵌套的 但是实际的作用域还是在fn()n那 因此x还是去全局作用域寻找
4、闭包
1)如何产生闭包:
* 当一个嵌套的子函数引用了嵌套的外部(父)函数的变量(函数)时,就产生了闭包。
2)闭包到底是什么?
* 闭包是嵌套的内部函数
* 包含被引用变量(函数)的对象
function fn1(){
var a=2
function fn2(){
console.log(a)
}
return fn2
}
fn1()
3)产生闭包的条件?
* 函数嵌套
* 内部函数引用外部函数的数据(变量/函数)
* 产生几个闭包由调用了几次外部函数决定
function fn1(){
var a=2
function fn2(){
a++
console.log(a)
}
return fn2
}
var f=fn1()
f() //3
f() //4
4)闭包的作用
* 使用函数内部的变量在函数执行完后,依然存活在内存中(延长局部变量的生命)
* 让函数外部可以操作(读写)到函数内部的数据(变量/函数)
注:* 函数执行完后,函数内部声明的局部变量一般是不存在,存在于闭包之中的才会存在
* 在函数外部能直接访问内部的局部变量吗?不能 但可以通过闭包让外操作他问
5)闭包的应用
* 具有特定功能的js文件
* 将所有的数据和功能都封装在一个函数中(私有的)
* 向外只暴露一个包含n个方法的对象或函数
* 模块的使用者,只需要通过模块暴露的对象调用方法来实现对应的功能
以下示例两种封装的js方式,使用到了闭包
function test(){
var msg="sjw"
//操作数据的函数
function dosomething(){
console.log("dosomething"+msg.toLocaleUpperCase())
}
function doanything(){
console.log("doanything"+msg.toLocaleUpperCase())
}
return message1={
dosomething:dosomething,
doanything:doanything
}
}
var message1=test()
message1.dosomething() //dosomethingSJW
message1.dosomething() //doanythingSJW
-------------------------------------------------------------------------
;(function(window){
//私有的数据
var msg="sjw"
//操作数据的函数
function dosomething(){
console.log("dosomething"+msg.toLocaleUpperCase())
}
function doanything(){
console.log("doanything"+msg.toLocaleUpperCase())
}
window.message={
dosomething:dosomething,
doanything:doanything
}
})(window)
message.doanything() //doanythingSJW
message.dosomething()//dosomethingSJW
6)闭包的缺点
* 函数执行完后,函数内部的局部变量没有释放,占用内存时间变成
* 容易造成内存泄漏(空间占用着不使用,其他的变量也没法使用,这就是内存泄漏)
解决办法:
* 能不用就不用(不现实)
* 及时释放 f=null
7)内存溢出和内存泄漏
*内存溢出:
* 一种程序运行出现的错误
* 当程序运行需要的内存超过剩余的内存时,就抛出内存溢出的错误
* 内存泄漏
* 占用的内存没有及时释放
* 内存泄漏积累的多了就容易导致内存溢出
* 意外的全局变量(直接赋值相当于全局变量 a=3)会造成内存泄漏
* 启动循环定时器后不清理容易造成内存泄漏
* 闭包会造成闭包泄漏 (f=null释放掉,不释放就造成泄漏)
8)面试题
var name="the window"
var object={
name:"my object",
getNameFunc:function(){
return function(){
return this.name
}
}
}
alert(object.getNameFunc()()) //the window
var name2="the window"
var object2={
name2:"my object2",
getNameFunc:function(){
var that=this
return function(){
return that.name2
}
}
}
alert(object2.getNameFunc()()) //the object2
-----------------------------------------------------------------------------
function fun(n,o){
console.log(o)
return{
fun:function(m){
return fun(m,n)
}
}
}
var a=fun(0)
a.fun(1)
a.fun(2)
a.fun(3)
var b=fun(0).fun(1).fun(2).fun(3)
var c=fun(0).fun(1)
c.fun(2)
c.fun(3)
5、对象创建模式
1)Object构造函数模式
* 先创建空Object对象,再动态添加属性方法
* 适用场景:起始对象的数据是不确定的
* 问题:语句太多
2)对象字面量模式
*{ }创建对象,指定属性方法
* 适用场景:起始时内部的数据是确定的
* 问题:创建多个对象,有重复代码
3)工厂模式(生产一个新的对象,不常用)
* 通过工厂函数动态创建对象并返回
function createPerson(name,age){
var obj={
name:name,
age:age
setName:function(name){
this.name=name
}
}
return obj
}
var p1=createPerson("Tom",12)
var p2=createPerson("Jack",15)
* 适用场景:创建多个对象
* 问题:对象没有一个具体的类型,都是Object类型
4)自定义构造函数类型
function Person(name,age){
this.name=name
this.age=age
this.setName:function(name){
this.name=name
}
}
var p1=new Person("jack",20)
* 适用场景:创建多个不同类型的对象
* 问题:内存的浪费,因为方法都一样,在每个实例对象上
5)构造函数+原型的组合模式
* 套路:自定义构造函数,属性在函数中初始化,方法添加到原型对象上
* 适用场景:需要多个类型确定的对象
function Person(name,age){
this.name=name
this.age=age
}
Person.prototype.setName=function(name){
this.name=name
}
6、继承模式
1)原型链继承
2)组合继承(原型链+借用构造函数的组合继承)
* 利用原型链实现对父类型对象的方法继承
* 利用super( )借用父类型构造函数初始化相同
//父亲
function Person(name,age){
this.name=name
this.age=age
}
//把方法放在父亲的原型链上
Person.prototype.setName=function(name){
this.name=name
}
//子类型 其中call实现了子类用假继承操作属性
function Student(name,age,price){
Person.call(this,name,age)
this.price=price
}
//子类型的原型是父类的实例--实现了继承 通过原型链继承使得子类可以使用父类的方法
Student.prototype = new Person()
//修正constructor属性???
Student.prototype.constructor=Student
//子类的方法放在父类的实例上
Student.prototype.setPrice=function(price){
this.price=price
}
//新建了Student实例 但是他的原型相当于父类的实例 父类的实例又指向父类的原型 构成原型链
var s=new Student("Tom",24,16000)
//父类的属性和方法都能用
console.log(s.name,s.price,s.age) //Tom 16000 24
//因此可以使用父类的方法
s.setName("Jack")
//放在父类实例上的自己的方法
s.setPrice(1000)
console.log(s.name,s.price,s.age) //Jack 1000 24
7、线程机制和事件机制
1)进程和线程
* 进程:程序的一次执行,他占有一片独立的空间
* 线程:是进程内的一个独立执行单元,是程序执行的一个完整流程,CPU的最小调度单元
* 进程中的多线程,数据可以共享;而不同进程之间的数据不可以共享
* js是单线程 (但是H5中的Web Workers可以多线程运行)
* 浏览器运行是多线程运行的
2)定时器是如何实现的?
* 定时器并不能保证真正定时执行
* 一般会延迟一丁点(可以接受),也可能延迟很长时间(无法接受) 先执行for等耗费时间
* 事件循环模型
3)js是单线程执行的/事件循环模型
* alert( )会暂时当前主线程的执行,同时暂停时间计时,点击确认后恢复执行
事件循环:从任务队列中循环取出回调函数放入执行栈中处理(一个接一个)
具体见宏队列,微队列笔记等等