作用域
作用域是存储管理变量的规则,代码执行开辟栈内存,确定当前代码对这些标识符的访问权限
变量和函数都有作用域
- 在全局下(非函数内):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操作符调用的普通函数,没有所谓的“构造函数”,只有对于函数的“构造调用”
- 创建空对象p: let p = { }
- 原型链指向 构造器Person的原型 p.__proto__ = Person.prototype
- 新对象绑定到函数调用的this( Person.call(p) )
- 当存在显示的返回时,返回 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;
}
- 创建一个新的对象,并返回。符合new函数的功能。
- 将类数组转成数组 传入myNew函数的第一个参数。
- 将第一个参数的prototype与要返回的对象建立关联。
- 使用apply,改变构造函数的this指向,使其指向新对象,这样,obj就可以访问到构造函数中的属性了。
- 返回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方法是返回改变指向的函数
箭头函数和普通函数的区别
-
箭头函数没有
prototype
(原型),所以本身没有this -
没有this所以不能作为构造函数使用,没有constructor就不能使用new。 构造函数是通过 new 关键字来生成对象实例,生成对象实例的过程也是通过构造函数给实例绑定 this 的过程,而箭头函数没有自己的 this。因此不能使用箭头作为构造函数,也就不能通过 new 操作符来调用箭头函数。
-
箭头函数的this指向声明时所在作用域的第一个普通函数(普通函数的this,在调用时确认)
-
call、apply、bind不会改变箭头函数this指向,会改变普通函数this指向
- 被继承的普通函数的this指向改变,箭头函数的this指向会跟着改变
-
箭头外层没有普通函数,非严格模式下this指向
window
(全局对象),严格模式下指向undefined -
箭头函数的this指向全局对象,会报arguments(不定参)未声明的错误。
箭头函数的this如果指向普通函数,它的
argument(类数组对象)
继承于该普通函数。 -
取而代之用rest参数(...arg展开运算符) 解决,rest必须是最后一位参数
-
箭头函数不支持
new.target
new.target
是ES6新引入的属性,new.target返回使用new方法调用类时的类的名称,子类继承父类时,new.target会返回子类主要用于确定构造函数是否为new调用的
限制类的调用方法,判断new.target是不是未定义
用于写出只能被继承使用的类
- 箭头函数不支持重命名函数参数,普通函数的函数参数支持重命名
-
箭头函数不能当做Generator函数,不能使用yield关键字
使用箭头函数注意事项
var func1 = () => { foo: 1 }; // 想返回一个对象,花括号被当成多条语句来解析,执行后返回undefined var func2 = () => ( {foo: 1} ); // 用圆括号是正确的写法