作用域 闭包 new过程 箭头函数和普通函数

作用域

作用域是存储管理变量的规则,代码执行开辟栈内存确定当前代码对这些标识符的访问权限

变量和函数都有作用域

  • 在全局下(非函数内):var a = window.a = a
  • 在函数下:var b只在函数内可以调用,window.b = b都可以在全局下使用

  • function a全局函数
  • 在函数下:function b非全局函数
  • 当function赋值给了一个变量,例如全局变量 a.b = function()可以全局使用

词法作用域:

就是定义在词法阶段的作用域,在引擎编译前,以及代码生成前就决定了词法作用域。

已经决定了的词法作用域不会再改变,但是可以通过eval()和with欺骗引擎

eval() 接受一个字符串参数,字符串里面可以是表达式、js语句等当作原先写代码时就存在的

with用在对象

这两种欺骗方法会导致性能下降,因为js本身有一套优化:预先确定所有变量和函数定义位置,方便执行时快速找到。但是这两个方法是动态的,会改变原先的作用域,不知道改变后的位置怎样变化,所以干脆就不优化,所以性能变差(不建议使用)

遮蔽效应:

由于作用域查找,是找到第一个匹配的标识符就停止了,在多个作用域都通过var声明的变量,在较内部的作用域中会遮蔽外部的

函数作用域:

可以通过this改变(用call、apply、bind改变this指向)

函数作用域和块作用域的作用:

隐藏内部的变量和函数,防止命名冲突问题

匿名函数的缺点:

1、可读性差

2、没有名称标识符,调试困难

而函数声明不能省略函数名,所以匿名函数只能在包装函数中使用,因为包装函数用括号包裹,会变成一个函数表达式,而不是函数声明

3、无法引用函数自身

在事件触发后事件监听器需要解绑自身时无法引用

作用域链

当在内部函数中,需要访问一个变量的时候,首先会访问函数本身的变量对象,是否有这个变量,如果没有,那么会继续往外部环境查找,直到全局作用域。如果在某个变量对象中找到则使用该变量对象中的变量值。

  • 作用域链的前端,始终都是当前执行的代码所在环境的变量对象
  • 作用域链中的下一个对象来自于外部环境,而在下一个变量对象则来自下一个外部环境,一直到全局执行环境
  • 全局执行环境的变量对象始终都是作用域链上的最后一个对象

在A函数中创建B函数,在B函数中创建C函数,那么作用域链就是C->B->A。

引擎是什么?

引擎运行编译器生成的代码

引擎在作用域查找变量的两种方法:RHS  LHS

RHS引用: console.log(a)

当前作用域中找不到该变量,就往外层找,直到全局作用域也找不到就会报一个ReferenceError的异常

LHS引用: a = 2

如果到全局作用域也找不到,在非严格模式下,会自动声明,返回给引擎

如果在作用域中找到声明过的变量,但是进行了不合理的操作,比如对一个非函数类型的值进行函数调用,或者视图获取null undefined类型的值中的属性,就会抛出TypeError异常

编译器是什么

具体做了词法分析、语法分析、代码生成,生成的代码给引擎编译执行

 严格模式 非严格模式区别:js严格模式与非严格模式的区别 - 骨子里的钟 - 博客园

new关键字生成实例过程p91

js的new操作符做了哪些事情 - 嗯嗯呢 - 博客园

构造函数只是被new操作符调用的普通函数,没有所谓的“构造函数”,只有对于函数的“构造调用”

  1. 创建空对象p: let p = { } 
  2. 原型链指向 构造器Person的原型  p.__proto__ = Person.prototype
  3. 新对象绑定到函数调用的this(  Person.call(p)  )
  4. 当存在显示的返回时,返回 return 后面的内容,新建的空对象作废。否则返回新对象。

        若此处Person为箭头函数,而没有自己的this,call()函数无法改变箭头函数的指向,也就无法指向p


1、符合原理的描述:

• 以构造器的prototype属性为原型,创建新对象;
• 将this ( 也就是上一句中的新对象 ) 和调用参数 传给构造器,执行;
• 如果构造器没有手动返回对象,则返回第一步创建的对象


2、实现一个简单的new方法:

​​function myNew(){
    const obj = new Object();
    Constructor = Array.prototype.slice.call(arguments);   //这里的call作用不在this
    obj.__proto__ = Constructor.prototype;
    let ret = Constructor.apply(obj,arguments); // 判断构造函数是否存在返回值
    return typeof ret === 'object'? ret : obj;
}
  1. 创建一个新的对象,并返回。符合new函数的功能。
  2. 将类数组转成数组 传入myNew函数的第一个参数。
  3. 将第一个参数的prototype与要返回的对象建立关联。
  4. 使用apply,改变构造函数的this指向,使其指向新对象,这样,obj就可以访问到构造函数中的属性了。
  5. 返回obj。

作者:你的声先生
链接:https://juejin.cn/post/6844904005223579655


3、构造函数能返回什么?默认返回什么?

        new一个构造函数总是会返回一个对象,默认返回this所指向的对象。如果我们没有在构造函数内为this赋予任何属性,则会返回一个集成了构造函数原型,没有自己属性的'空对象'。没在构造函数内写return语句,也会隐性的返回this。

精读JavaScript模式(三),new一个构造函数究竟发生了什么? - 听风是风 - 博客园


4、不使用new调用构造函数会怎样?

语法也不会出错,但函数中的this会指向全局对象(非严格模式是window)。


5、构造函数内能自定义对象吗?

var Person = function () {
  return {
    name:'echo'
  }
};
Person.prototype.sayName = function () {
  console.log(1);
};

me.sayName();//报错,自定义对象未指向Person,没继承Person的方法
you.sayName();//报错,同上

能。但这样丢失了原型,实例不会继承原型上的属性。


6、不使用new情况下实例也能继承Person属性吗?

当然有,比如调用自身的构造函数:

var Person = function () {
  if(!(this instanceof Person)){
    return new Person();
  }
  this.name = "echo"
};
Person.prototype.sayName = function () {
  console.log(1);
};
var me = new Person();
var you = Person();
me.name;//echo
you.name;//echo
me.sayName();//1
you.sayName();//1

精读JavaScript模式(三),new一个构造函数究竟发生了什么? - 听风是风 - 博客园

什么是this、call、apply、bind?掘金

      后三者作用都是动态改变了上下文,也就是改变了this的指向

      call、apply立即执行  bind不立即执行

 可忽略第一个参数

        Dad.call(this,height)              //call方法接收的是若干个参数列表

        Dad.apply(this,[height])        //apply方法接受的是一个包含多个的参数数组

        Dad.bind(this)(height)        //bind方法是返回改变指向的函数

箭头函数和普通函数的区别 

  1. 箭头函数没有prototype(原型),所以本身没有this

  2. 没有this所以不能作为构造函数使用,没有constructor就不能使用new。                                构造函数是通过 new 关键字来生成对象实例,生成对象实例的过程也是通过构造函数给实例绑定 this 的过程,而箭头函数没有自己的 this。因此不能使用箭头作为构造函数,也就不能通过 new 操作符来调用箭头函数。

  3. 箭头函数的this指向声明时所在作用域的第一个普通函数(普通函数的this,在调用时确认)

  4. call、apply、bind不会改变箭头函数this指向,会改变普通函数this指向

  5. 被继承的普通函数的this指向改变,箭头函数的this指向会跟着改变
  6. 箭头外层没有普通函数,非严格模式下this指向window(全局对象),严格模式下指向undefined

  7. 箭头函数的this指向全局对象,会报arguments(不定参)未声明的错误。

    箭头函数的this如果指向普通函数,它的argument(类数组对象)继承于该普通函数。

  8. 取而代之用rest参数(...arg展开运算符) 解决,rest必须是最后一位参数

  9. 箭头函数不支持new.target

    new.target是ES6新引入的属性,new.target返回使用new方法调用类时的类的名称,子类继承父类时,new.target会返回子类

    主要用于确定构造函数是否为new调用的 

    限制类的调用方法,判断new.target是不是未定义

    用于写出只能被继承使用的类

  10. 箭头函数不支持重命名函数参数,普通函数的函数参数支持重命名
  11. 箭头函数不能当做Generator函数,不能使用yield关键字

       

使用箭头函数注意事项

var func1 = () => { foo: 1 };      // 想返回一个对象,花括号被当成多条语句来解析,执行后返回undefined var func2 = () => (  {foo: 1} );     // 用圆括号是正确的写法

掘金

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值