第一章 作用域深入和面向对象
预解释
当浏览器开始解析js代码的时候,首先看当前运行环境(作用域)内带var和function,带var的变量会提前声明(预解释)但是不会赋值,带function的会提前声明并赋值。带var变量提前声明的时候并不会被赋值,但是有一个默认的undefined值。当代码执行过后才会赋值。
堆栈内存: 代码运行的环境也是作用域是栈内存,而用来保存值的内存是堆内存.基本数据类型都存在栈内存里
- 预解释只看等号左面的变量,并不会看你的值是什么
- 我们的预解释只发生在当前作用域
console.log(num); //undefined
console.log(obj);
console.log(sum);
var num = 12; //赋值
console.log(num); //12
var obj = {'name': 'tianxi', age: 30}; //对象类型
console.log(obj);
//console.log(haha);//??
// var a = function () {};
//预解释只看等号左面的变量,并不会看你的值是什么
function sum(num1, num2) { //当代码执行到这的时候,声明和赋值都已经结束,直接跳过
function haha() {
}; //我们的预解释只发生在当前作用域
var total = 0;
total = num1 + num2;
}
函数的运行
- 如果有形参是形参赋值
- 预解释
- 代码逐行执行
区别私有变量还是全局变量:函数运行的时候,函数体内如有带var就是私有变量,如果是形参也可以理解是一个私有变量。代码在执行的时候,首先查找当前运行环境内(作用域,栈)的私有变量,如果有直接用,如果没有去上一级作用域去查找,如果有就拿来用,如果没有一直查找到顶级的window全局作用域,如果没有就报错了 not defined,我们把这种查找机制叫做作用域链
在全局作用域下,加var和不加var的区别1. 是否被提前声明 2. 不加var那么就是一个赋值过程,相当于给window添加了一个属性并且赋值
当函数在运行的时候就会产生一个私有的作用域,并且这个作用域内的变量也是私有变量。并且这个私有变量在外访问不到,我们把这种函数运行的时候产生的私有作用域里的私有变量不受外界干扰的这种机制叫做闭包
//console.log(total); //undefined
//total = 0; //
//console.log(window.total); //
function sum(num1, num2) { //num1 = 10,num2 = 10
console.log(total); //undefined
total = num1 + num2;
console.log(total); //30
return function (){
console.log('ff');
}
}
sum(10, 20);
var f = sum(19,40); //
f();
console.log(total); //0
//function b(x){} b();
//console.log(total);
//undefined,0,30,30 , //0,30,30
代码示例
/* function fn(){
total = 100; //赋值的过程,直接赋值给window
// var total = 100; //这个是私有变量跟外面没关系
}
fn();
console.log(total);*/
////////////////////////////////////
var num = 12;
function fn(){
var num = 100;
return function (){
var x;
console.log(num);
}
}
var f = fn(); //100
f(); // x的变量才会被预解释
/*
函数运行运行时候上一级作用域只和在哪里定义有关系
* */
//12,100,undefined Error
/*
*
*
*
* */
/*var obj = {name:'haha',age:7};
console.log('name' in obj);*/
/*
* 预解释的时候
* 1 无论条件是否成立,都会预解释
* 2 预解释的时候带var关键字之看等号左面
* 3 在全局作用域下的自执行函数不被预解释,预解释只发生在当前作用域
* 4 return下面的代码仍然会被预解释,但是return出来的值即使你是一个函数也不会被预解释
*
*
*
*
*
* */
//1 无论条件是否成立,都会预解释
console.log(total); //undefined
if('total' in window){
var total = 6;
}
console.log(total); //6,undefined.
//2 预解释的时候带var关键字之看等号左面
//a();
var a = function (){
alert();
}
//3 自执行函数不被预解释
!function (){
var b = 0;
}()
//4 return下面的代码仍然会被预解释,但是return出来的值即使你是一个函数也不会被预解释
function fn(){
console.log(num); //??undefined
return function (){
console.log(num);
}
alert();
var num = 9;
}
fn();
//5
foo();
function foo(){
console.log(1);
}
foo();
var foo = 'haha'; //代码执行到这的时候,我们原来的函数引用地址已经被破坏,并且把foo赋值一个字符串
foo(); //相当于让一个字符串去执行,不是函数所以报foo is not function
function foo(){
console.log(2);
}
foo();
//undefined,1,unKonwn,unKnown
//2,2,ERROR
//
//2,2,ERROR
//
//2,2,ERROR
作用域销毁和内存释放
作用域销毁和内存释放问题
- 如果让一个堆内存释放掉那么就让这个堆内存没有被其他变量引用,如果函数在执行的后,函数体>内的某一部分被函数体外占用,那么我们运行后返回的堆内存和运行时产生的作用域(栈内存)都不会被释放掉.如果没有被占用,浏览器会主动回收内存空间
- 函数运行的时候,在函数体内定义的匿名函数赋值给函数体外的dom元素的事件属性,内存也不会被释放掉
- 函数运行之后留下来的返回值是一个函数,立刻又被执行一次,这样是不会被立刻释放掉的。
var obj = {name:'tianxi',age:30}; //这个对象的堆内存地址只要被obj这个变量引用,这个堆内存就不会被释放;
obj = null; //把一个空的指针赋值给obj,这样obj以前的堆内存就会被释放掉
var num = 12;
function fn(){ //fn在预解释的时候已经赋值一个堆内存
//var num = 100;
return function (){ //仍然是一个引用类型,堆内存地址xxxfff111
var x;
console.log(num);
}
}
var f = fn(); //是把fn运行留下来的结果赋值给f(并且我们发现return出来是一个函数,相当于把函数的引用地址赋值给f变量),函数运行: 1 形参赋值 2 预解释 3 代码执行
//函数运行的时候开辟一个新的私有作用域
f(); //就是xxxfff111运行
///////////////////////////////////////
var oDiv = document.getElementById('div1'); //通过dom获取,是一个dom对象
console.dir(oDiv);//
/*(function (j){
oDiv['onclick'] = function (){
console.log('点我a');
//
//oli[j]
//this
}
})(i);
*/
/////////////////////////////////////
function test(){
return function (){
console.log(1);
}
}
test()();
this关键字
this 我们主要研究的是函数里的this
1. 给元素绑定事件,当事件触发的时候,this就是事件触发时的元素
2. this是谁跟函数在哪里定义和在哪里执行没关系,就看调用函数的主体是谁,其实也就是看函数执行的时候"."前面是谁,this就是谁
3. 自执行函数的this是window,无论在哪都是window
4. 在构造函数中的this是当前实例
5. call和apply都会强制改变this。
//console.log(this);
/*document.getElementById('div1').onclick = function (){
console.log(this);
}*/
function fn(){ //这是函数的定义
console.log(this);
}
//window.fn(); //这才是函数的执行,点前面是window.所以this就是window
/* var obj = {
fn : fn
}*/
//obj.fn(); //
/* var fo = {
x : function (){
console.log(this);
fn(); //this ==> window
}
}
fo.x(); //fo*/
var haha = {
y : (function (){
console.log(this);
return function (){
console.log(this);
}
})()
}
haha.y(); //
/* ~function (){
console.log(this);
fn();
}()*/
// 一个自定义类 构造函数
function FE(){
//console.log(this);
this.x = 'ga';
}
FE();//当成一个函数去运行
var ff = new FE(); //就把FE当成一个类去运行,而fe就是FE的一个实例
console.log(ff);
代码示例
var num = 20; //20*=3==》60 通过地30行执行fn()把window.num *= 4了。 240
//window.num *= 3;
var obj = {
num : 30, //120
fn : (function (num){ //num = 20
this.num *= 3; //this=>window.num*=3;
//console.log(num+'aa');
num += 20; //20+=20 = 40;//相当于当前作用域内的num变量修改成40了
//console.log(num+"aa");
var num = 45; //因为和形参是相同的,所以不被预解释,这个var在此处有和没有是一样的效果 num = 45; num+=20 ==>65
return function (){
this.num *= 4; //第30行执行的时候,我们的this是window,window.num *=4;
num += 20;
console.log(num); //65
}
})(this.num) //??this是谁window ==> window.num
}
var fn = obj.fn; //把obj.fn的引用地址告诉了fn
fn(); //65 xxxfff11
fn(); //xxxfff111
fn();
fn();
fn();
obj.fn();// this ??? 85
console.log(window.num,obj.num);
/* return function (){
this.num -= 30;
num += 15;
console.log(num);
}*/
var num = 20
function a(){
num += 20;
}
a();
console.log(num);
综合练习题
点赞效果
觉得我很帅的点赞 0
面向对象
首先面向对象是什么,为什么要面向对象。
因为JavaScript对每个创建的对象都会自动设置一个原型(谷歌火狐中是proto),指向它的原型对象prototype,举个栗子:
function person() {}
那么person的原型链就是:
person(proto) > Function.prototype(proto) > Object.prototype
所以最终的结果都是指向原型对象,就称为面向对象
那么问题来了,怎么面向对象呢?其实就是问怎么创建对象,创建对象有哪几种方法。如下:
单例模式
/*
* 单例模式: 就是一个破对象, 也是命名空间模式。(namespace)是js编程中最常用的模块化开发模式,在工作过程中也会经常看到.
* */
function Table() { //用function的方式去创建一个人为的一个类
}
var table1 = new Table(); //用实例创建的方式,创建了一个具体化的类也就是一个shi'li
var person = { //我自己
name : 'tianxi',
age : 30,
height : '175cm',
weight : '90kg',
code : function (){
//我有一个写代码的功能
},
change : function (){
},
utils:{
}
}
var person2 = {
name : 'youcheng',
height : '180cm',
wirteJs : function (){
//这个方法是youcheng的,但是我不一定会
},
change: function (){
}
}
var Tx = {
change: function (){}
}
var zy = {
change : function (){}
}
//person.code();
//person2.writeJs();
console.log(person);
工厂模式
/*
* 工厂模式: 解决不能批量生产,它是一个破函数,把实现相同功能的属性拿出来放到一个函数里了,我们把这种方式也叫函数的封装。但是工厂模式不能解决实例识别的问题
* 面向对象:函数的封装,继承(子类继承父类的方法和属性等,祖传),多态:包括重载和重写,js中只有重写,但是却可以模拟重载.
* */
function table(){ //我们的车床
var obj = {};
obj.width = 200;
obj.height = 300;
obj.fn = function (){
//桌子能干啥,我也不知道
}
return obj;
}
var tab1 = table(); //相当于车床通电运行一次就给我造一个桌子,是不是要给我留下东西呀
tab1.fn2 = function (){
//烧火吧
}
var tab2 = table(); //
//new table();
({}).toString == '[object Object]'; //分别代表的是数据类型和所属的类
[].toString == "";
//[1,2,3].toString() = '1,2,3' //在定义数组的toString方法的时候被重写了
函数构造模式
/*
* 构造函数模式 (类) 实例,构造函数在new的时候都会默认创建一个实例对象,并且还会给返回,构造函数运算的时候,如果return一个基本数据类型的时候,不会改变默认的返回值,如果return一个引用数据类型的时候,默认返回的实例对象就被改变了
* 区别:如果当作构造函数运行会默认返回一个实例,如果一个普通函数运行什么也没返回
* */
function Table(width){
//构造函数中的this是我们的当前实例
this.width = width;
this.code = function (){
console.log('桌子也能codeing');
}
//return '123';
//return {'name' : 'allen'}
}
var table4 = new Table(100); // 当作普通函数运行
console.log(table4);
var table1 = new Table; //构造函数中没有参数的时候我们的时候小括号可以省略
var table2 = new Table(100);
console.log(table2);
var table3 = new Table(200);
console.log(table3);
//console.log(table2.code == table3.code);//???false
/*
* 构造函数中的私有变量跟实例没有关系
* 我们可以用hasOwnProperty来检测是否含有这个私有属性
* */
function Fn(){
var num = 10; //在实例运算的时候,这个里的私有变量跟实例没有关系
this.x = 100; //window.x = 100 相当于给window添加了x的属性值为100
this.getX = function (){ //相当于给window添加了一个getX变量,至始一个匿名函数
return (this.x); //100
}
}
var f = new Fn(); // f = {x:100,getX:function (){console.log(this.x)}}
console.log(f);
console.log(f.hasOwnProperty('X')) //
//console.log(f.num); //undefined
//var k = Fn(); //当作普通函数运行吧
//console.log(f.getX()); //undefined
for(var attr in f){
console.log(attr);
}
Object.prototype.toString.call(); //改变this
原型模式
/*
* 原型 prototype
* 1 当我们声明了一个函数(构造函数,类)的时候,天生自带了一个prototype的属性。并且这个prototype的值也是一个对象类型的。这个对象类型值也有一个天生自带的属性叫constructor并且这个属性的值是函数(构造函数,类)本身
* 2 这个类的实例也会有一个叫做__proto__的天生自带的属性,并且这个属性的值也是一个对象类型的。这个值是这个实例所属类的原型.
* 3 每一个引用类型都有一个天生自带的属性叫__proto__,所以说我们的prototype的值也有天生自带一个__proto__的属性。并且这个属性的值也是一个对象类型,一直到我们的基类Object
* 4 通过类的原型添加的属性和方法都是公有的,每个实例都会自带
* 5 一个实例的方法在运行的时候,如果这个方法是自己私有的,那么就直接用,如果不是私有的,那么通过__proto__去所属类的原型上去查找,如果还没有就通过原型的__proto__一直查到基类的Object.如果还没有报错,如果有就直接用了。我们把这种通过__proto__查找的机制叫做原型链.
*
* */
function FE(){ //
this.x = 100;
this.y = 200;
}
FE.prototype.code = function (){ //在FE的原型上添加了一个code属性,属性值是一个函数
//console.log('我是通过原型添加的')
//console.log(this.x);
}
//console.dir(FE);
//var fe1 = new FE();
//fe1.code(); //说明成功添加了一个code的属性
//console.dir(fe1);
var fe2 = new FE();
//fe2.code(); //fe2.x
fe2.__proto__.codeing = function (){
//alert('gaga');
}
//fe2.codeing();//?????
var fe3 = new FE();
//fe3.codeing();
//fe3.codeing = function (){
//alert('fe3');
//}
//console.log(fe2.codeing()); //??
//console.dir(fe3);
//fe3.__proto__.__proto__ == Object.prototype
for(var attr in fe3){
if(fe3.hasOwnProperty(attr)){
console.log(attr);
}
}
原型拓展
function Fn(){
this.x = 100;
this.y = 100;
this.getY = function (){
console.log(this.y);
}
}
//Fn.x = 100;
Fn.prototype = {
constructor : Fn,
y : 100,
getX : function (){
console.log(this.x); //this? f.x //f.__proto__.x?
},
getY : function (){
console.log(this.y);
}
}
var f = new Fn(); //x = 100 y= 100 getX = function (){console.log(this.y)}
f.getX(); // __proto__ 去它所属类的原型上去查找
f.__proto__.getX();//undefied
Array.prototype.push = function (){
alert('push');
}
var a = [];
//a.push();
// 给原生对象扩展自定义方法
Array.prototype.quchong = function (){
//this ==> a [1,2,1 ,3,2,] a[a.length-1]
var obj = {};
for(var i=0; i
/*
*
* 批量设置共有的属性和方法
* */
function Girl(){
}
Girl.prototype = { //用一个新的对象把原来的原型的地址替换了
constructor : Girl,
a : function (){},
b : function (){},
c : function (){}
/*push:...*/
}
Girl.prototype.a
Girl.prototype.b
Girl.prototype.c
/* //画图说明原型链查找机制 ==> hasOwnProperty
tab1.__proto__.fn == tab2.__proto__.fn;
tab1.__proto__.fn == tab2.fn; //如果你tab2是否一个私有的fn
tab1.__proto__.fn == Table.prototype.fn;*/