预编译
函数作用域预编译
-
创建AO对象(活动对象)
-
找形参和变量的声明,作为AO对象的属性名,值为undefined
-
实参和形参相互统一(也就是将实参的值赋给形参)
-
找函数声明,如果与变量同名,会覆盖变量的声明
-
function fn(a,c){ console.log(a); // function a(){} var a = 123; console.log(a); // 123 console.log(c); // function c(){} function a(){} if (false){ var d = 678; } console.log(d); // undefined console.log(b); // undefined var b = function(){} console.log(b); // function(){} function c(){} console.log(c); // function c(){} } fn(1,2); /* ao{ 第一步 第二步 第三步 a: undefined 1 function a(){} c: undefined 2 function c(){} d: undefined b: undefined } */
对象的getter和setter函数
var obj = {
name: "chen",
age: 12,
get getterAttr(){
// 函数不能带参数
// console.log(this);
console.log("getter", this.name);
return "这是调用属性getterAttr的getter函数";
}
}
Object.defineProperty(obj, "testAttr", {
get: function(){ console.log("testAttr"); return "testAttr"}
});
// console.log(obj);
console.log(obj.getterAttr);
console.log(obj.testAttr);
// getter chen
// 这是调用属性getterAttr的getter函数
// testAttr
// testAttr
this的指向
在函数执行的时候进行this的绑定
-
函数直接调用 func(a) 等价与 func.call(window,a),this指向window
-
函数作为对象的方法被调用,就指向这个函数
-
如果函数是一个构造函数,则this指向一个新的对象
-
在严格模式下:函数直接调用,this不是全局对象,而是undefined
-
var name = 222; var a = { name: 111, say: function(){ console.log(this.name); } }; var fun = a.say; fun(); // 222 fun.call(window); a.say(); // 111 a.say.call(a); var b = { name: 333, say: function(fn){ fn(); // fn.call(window); } } b.say(a.say); // 222 b.say = a.say; b.say(); // 333 b.say.call(b)
箭头函数的this
- 箭头函数中的this是在定义函数的时候绑定的,而不是在执行函数的时候绑定,箭头函数this指向固定化,实际原因是因为箭头函数没有this,导致内部的this就是外层代码块(父执行上下文)的this,正因为他没有this,所以不能用作构造函数
- 箭头函数没有this,没有arguments,用来设置匿名函数,不能用call/apply改变this的指向,会自动忽略
- this:该箭头函数所在的作用域指向的对象,也就是父级的this指针,可以用过修改父级的指针,达到修改箭头函数的this指向。
- 或者可以简单理解:然后箭头函数的this指针就是调用外层函数的对象,若箭头函数没有外层函数,那么就是window
var a = 222;
var obj = {
a: 333,
fn: ()=>{
console.log(this.a);
}
}
obj.fn(); // 222,this指向window
var a = 222;
var obj = {
a: 333,
b: {
fn: ()=>{
console.log(this.a);
}
}
}
obj.b.fn(); // 222
var a = 222;
var obj = {
a: 333,
b: function(){
var fn = () => console.log(this.a);
return fn;
}
}
obj.b()(); // 333
作用域
含义:简单来说,就是有效访问变量的集合,js只有函数作用域和全局作用域,当然可以使用let,const定义块级作用域的变量,函数被调用时就会创建函数作用域,在函数作用域创建的前期,会进行函数作用域和全局作用域的预编译,这个函数作用域预编译阶段会创建一个活动对象OA,然后对函数内的形参和变量声明,将变量和形参名当做AO对象的属性,值设置为undefined;然后将函数实参的值赋值给形参,接着在函数体找到函数声明,若存在函数名与变量同名,则覆盖变量的声明;
在函数内对一个未用var声明的变量直接赋值,就会变成全局变量
不同的作用域,变量名可以重复,查找的时候,就是沿着作用域链往上查找,找到最近的
函数声明和变量声明都会提升,且函数声明优先级大于变量声明,
var a = "apple";
// var声明的变量没有块级作用域,不要被括号迷惑
if (true) {
var a = "bananer";
}
console.log(a); // bananer
script标签
async和defer不同之处是async加载完成后会自动执行脚本,defer加载完成后需要等待页面也加载完成才会执行代码,defer:异步加载,延迟执行
性能
- 注意作用域
- 避免全局查找,全局变量和函数的查找需要涉及作用域链上的查找
- 避免使用with语句,因为with语句会创建自己的作用域,因此会增加with语句执行代码的作用域链长度
- 选择正确的方法
- 访问对象的属性O(n),因为要搜索原型链,所以遇到对象的查找,找到后可以存放起来;数组和变量,O(1);
- 优化循环,例如可以保存len的值,i<len;替换i<arr.len;
- 展开循环
- 避免双重解释:就是字符串的内容是js代码;初始解析不能识别,代码运行的时候还得新开启一个解析器来解析新的代码,开销太大
- 其他:原生方法快,switch语句快,位运算快
- 最小化语句数
- 多个变量声明写成一个语句
- 插入迭代值:var name = arr[i++];
- 使用数据和对象字面量
- 优化DOM操作
- 使用innerHTML,文档片段createDocumentFragment();
- 事件代理(事件委托):利用了事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件
- HTMLCollection(注意保存):html元素的集合,例如:document.getElementByTagName
- 部署:压缩代码
h5的新api
- requestAnimationFrame(callback):告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行(为何要引入:因为setTimeout、setInterval设置delay的时间,这个时候不一定跟浏览器的刷新频率一致,所以可能会出现丢帧或者动画效果生硬的问题)
严格模式下的有关限制
- this为null,undefined时,不会自动转换为window对象(call,apply,bind)
- 不能对为声明的变量进行赋值操作