keywords:变量提升,作用域,this,面向对象
作用域
当浏览器加载页面时,会形成一个全局作用域。
作用域:代码运行的环境,栈内存。
全局变量:运行在全局作用域中定义的变量。
(全局/私有作用域)不写
var
定义变量,会变成window
的属性:
a = 100;
相当于window.a = 100;
console.log(a);
// a is not defined 原因:浏览器认为a
是一个值,必须知道a
的数据类型
私有作用域:函数执行时,首先会形成私有作用域,供函数体中的代码执行,栈内存。函数每执行一次,会形成一个新的私有作用域。
局部/私有变量:在私有作用域定义的变量,一般是函数体中的变量和函数的形参。
函数体中可以访问外层作用域中的变量,但是外层作用域不能访问函数体中的变量。
标记查找从内往外寻找,如果找到停止查找,如果在最外层作用域都没有找到,报错。
私有作用域作用:
- 闭包:函数执行会形成一个私有作用域,保护函数体内的私有变量不受外部干扰。
- 存储值
全局变量与私有变量的案例:
var a,
b,
fn = function() {
var a = (b = 10); // 相当于 var a = 10;b=10
};
fn();
console.log(a, b); // undefined 10
// 以上代码相当于
var a; //默认undefined
var b; //默认undefined
var fn = function() {
var a = 10; // 重新定义了一个名为a的私有变量
b = 10; // 给全局变量 b 赋值 10
};
fn();
console.log(a, b);
变量提升
在当前作用域下,代码执行之前,把带 var
的和 function
的变量或者函数,放到当前作用域执行栈最前边。
带 var
的变量只声明不定义,只有当代码执行到赋值语句时才会定义(赋值)。
function
进行变量提升时,不仅进行声明,还要定义(赋值,因为在这里已经赋值, 在函数执行时会跳过代码中的函数)。所以函数的定义发生在变量提升阶段,而不是函数执行阶段。
函数表达式 var fn = function(){}
变量提升时看做变量而不是函数 function
函数的执行流程:
- 形成一个私有作用域
- 形参赋值
- 变量提升
- 代码从上到下执行
- 作用域是否销毁
function sum(num) {
console.log(num);
function num() {}
console.log(num);
}
//1.形成私有作用域
//2.形参赋值:num=11
//3.变量提升:num = function sum(){}
//4.运行:
特殊情况:
-
不管条件是否成立,都要进行变量提升。
console.log(a); // undefined if (false) { var a = 10; //在全居中会提升 }
在新版本浏览器中,if 块中函数只声明不定义,在 IE10 及以下,if 块中函数不仅声明而且定义。
console.log(a); // undefined console.log(fn); // 在标准浏览器中输出 undefined,IE10及以下输出函数本身 if (false) { var a = 100; function fn() {} } // 一道关于变量提升的面试题 f = function() { return true; }; g = function() { return false; }; ~(function() { if (g() && [] == ![]) { // 标准浏览器输出 g is not a function;IE10及其以下浏览器继续执行 f = function() { return false; }; function g() { return true; } } })(); console.log(f()); // IE10及其以下浏览器 false console.log(g()); // IE10及其以下浏览器 false
-
变量提升只发生在等号
=
左边。 -
return
返回的内容不进行提升,但是return
语句之后的变量或者函数会提升。function sum() { // 提升的内容 a c b var a = 10; console.log(b); // 整个函数 b console.log(c); //undefined return function() { console.log("hello"); }; console.log(b); // 此处代码不执行 var c = 199; function b() { var d = 10; } } var f = sum(); f(); // hello
return function(){};
// 这里return
出去的是function(){}
的堆内存地址。return
返回的永远是一个值,如果 return 的是对象,返回的是对象的地址,若是基本数据类型的变量,则是原始值。 -
如果变量名重复,不再进行提升,会重新赋值
console.log(a); // undefind var a = 100; console.log(a); // 100 var a = function(){};//函数表达式
如果函数和var变量重复,只提升函数。
console.log(a);//函数 function a (){} var a = 100;
如果重复的时两个函数,提升并定义最后的一个
console.log(foo)//function foo(a){console.log(a)} function foo(){} function foo(a){ console.log(a) }
-
匿名函数不进行变量提升,自执行函数定义和执行一起完成
es6 中定义变量,通过 let
不进行变量提升。不容许重复声明。
const
定义常量,不容许修改值。
堆栈内存
var ary = [1, 2, 3, 4];
function f(ary) {
ary[0] = 0;
arguments[0] = []; // 相当于ary=[],形参换了地址
ary[0] = 100;
console.log(ary); //[100]
}
f(ary);
console.log(ary); //[0,2,3,4]
栈:存储基本数据类型 提供运行环境(作用域)
全局作用域销毁:关掉页面或者浏览器
私有作用域的销毁:一般情况下,函数执行完成后立即销毁,另外:
- 不销毁作用域:
- 函数执行后 return 一个引用数据类型
- return 出去的内容被外界的变量或对象接收
- 不立即销毁作用域:
- 作用域销毁与否不仅与函数内部有关,还有函数执行有关:
堆:存储引用类型
this 关键字
在作用域中有特殊意义:类似与指针,谁调用指向谁
- 全局下的
this
永远指向window
- 函数中的
this
要看函数执行前有没有.
,.
前边是谁,this
就是谁;如果没有.
,this
指向window
- 给元素的事件行为绑定方法,那么方法中的
this
指向当前被绑定的元素 - 自执行函数中的
this
永远指向window
- 回调函数中的
this
指向window
- 构造函数(类)中的
this
指向当前类的实例 apply
,call
,bind
中的this
指向第一个参数
面向对象
面向对象 :类的继承、封装、多态
对象:万物皆对象。
类:一类事物的高度概括。
js 中的类:
内置类:数据类型类,元素类
数据类型类:
Number String Boolean Null Undefined Object Array RegExp Function Date
元素类
#div —> HTMLDivElement–>HTMLElement–>Element –>Node–>EventTarget–>Object
自定义类
实例:类的具象化,都是对象类型。
HTMLCollection 元素集合
NodeList 节点集合
创建对象
数组的创建:如果只有一个参数,代表生成数组的 length,大于一个参数,代表目标数组的元素。
var arr1 = new Arrary(3); //[empty,empty,empty]
var arr2 = new Arrary(2, 3); // [2,3]
对象的创建:
var obj = new Object();
obj.a = 12;
console.log(obj); //{a:12}
单例模式
把描述同一件事物的不同属性放进同一个命名空间下,避免了全局变量的干扰和冲突,那么这种模式:单例模式
var obj = {
name: "kk",
age: 12
};
工厂模式
把实现同一功能的代码写在函数体中,当想实现类似功能,直接执行当前函数:
减少了代码的冗余,实现了代码的高内聚,低耦合——函数的封装,实现了批量化的生产。
function createObj(name, age) {
var obj = {};
obj.name = name;
obj.age = age;
return obj;
}
构造函数模式
命名规范:构造函数(类)一般首字母大写
构造函数执行详细步骤:
- 形成私有作用域
- 形参赋值(如果不传参,可以省略小括号)
- 变量提升
- 创建出一个空对象,把
this
指向这个空对象 - 代码从上往下运行(属性赋值…),返回实例(
this
) - 作用域是否销毁
如果给构造函数手动返回一个引用类型数据,会返回这个引用类型数据
浏览器禁止调用内置类 Null
、 Undefined
原型模式
-
每一个函数数据类型(普通函数、类(内置类、自定义类))都天生自带一个
prototype
属性,这个prototype
的属性值是一个对象数据类型的; -
prototype
这个对象中天生自带constructor
属性,constructor
的属性值指向了prototype
对应的那个类; -
每一个对象数据类型的(对象、数组、实例、prototype)都天生自带一个
__proto__
属性,属性的属性值指向当前实例所属类的原型;
当获取对象属性名对应的属性值,首先会判断是否为私有属性,如果私有属性中不存在,那么对象会默认通过__proto__
继续向上查找,如果上级也没有,那么会继续通过__proto__
向上查找,直到找到Object
的原型为止,如果Object
中也不存在,那么会输出undefined
.这样一级一级向上查找过程会形成原型链.
__proto__
不是 一个私有属性,用 Fn.prototype.hasOwnProperty(“__proto__
”);返回false
.
for-in
循环只遍历私有属性。
原型深入
函数的三种角色
- 普通函数
- 类-构造函数
- 对象
call apply bind
call
改变原函数this
指向。
不传参数,严格模式下指向undefined
,非严格指向window
;
参数为null
,严格模式下指向null
,非严格模式指向window
;
apply
改变原函数this
指向,参数为数组
bind
IE8 及以下不兼容
异常
try {
//执行代码
} catch (e) {
console.log(e); //e 异常
}
JSON
- 复合类型的值只能是数组或对象,不能是函数、正则表达式对象、日期对象。
- 简单类型的值只有四种:字符串、数值(必须以十进制表示)、布尔值和
null
(不能使用NaN
,Infinity
,-Infinity
和undefined
)。 - 字符串必须使用双引号表示,不能使用单引号。
- 对象的键名必须放在双引号里面。
- 数组或对象最后一个成员的后面,不能加逗号。
在 IE7 及以下不兼容
数据绑定
-
创建元素对象
document.creatElement("div"); appendChild();
-
文档碎片
document.createDocumentFragment();
-
字符串拼接
-
es6 模板字符串
继承
-
把 Parent 的实例的空间赋值给类 Child 的原型,那么类 Child 的实例不仅可以调用类 Parent 的公有属性,还可以调用 Parent 的实例的私有属性;这种继承方式就是原型继承
function Parent() {} function Child() {} Child.prototype = new Parent();
缺点:
-
Parent 的所有引用类属性会被所有 Child 类实例共享:
function Parent(){ this.arr=[]; } function Child(){} Child.prototype = new Parent(); var child1 = new Child(); child1.arr.push('b1'); var child2 = new Child(); console.log(child2.arr)//['b1'] //造成引用类属性 arr 被所有 Child 类实例滥用
-
在创建 Child 实例时,不能向 Parent 传参
-
-
call : 改变 Parent 中的 this 指向,让其指向 Child 中的 this( Child 的实例); Child 创建的实例就具有Parent 的私有属性;
function Parent () { this.names = ['kevin', 'daisy']; } function Child () { Parent.call(this); } var child1 = new Child(); child1.names.push('yayu'); console.log(child1.names); // ["kevin", "daisy", "yayu"] var child2 = new Child(); console.log(child2.names); // ["kevin", "daisy"]
缺点:
方法都在构造函数中定义,每次创建实例都会创建一边方法,只能继承私有属性。
-
中间类继承
__proto__
: 改变了对象数据类型中__proto__
的指向;function a() { // 在IE10 及以下是不兼容的; arguments.__proto__ = Array.prototype; arguments.sort(); }
-
寄生组合式继承
function Parent() { this.z = 100; } function Child() { Parent.call(this); // 解决私有问题 } Child.prototype = Object.create(Parent.prototype); // 公有属性 var b = new Child(); console.log(b);
-
ES6 继承
class Fn { constructor() {} } class Foo extends Fn { constructor() { super(); } } // es6 继承 继承私有和公有