JavaScript知识点扩展

JavaScript知识点扩展

作用域

作用域作用:

1.定义:变量 函数 或者成员在整个程序内可以被访问的范围;

2.也可用于信息隐藏

块作用域:{…}包含的代码就是一个块作用域,JS没有块级作用域;

动态作用域:函数内的成员是否可以被访问只有在运行的时候才能确定

function f(){
	alert(x);
}
function f1(){
	var x = 1;
	f();
}
function f2(){
	var x = 2;
	f();
}
f1();//报错:x没有定义,说明JS没有动态作用域
f2();

JS只有静态作用域lexical,也叫词法作用域或者闭包,在词法创建阶段就确定了相关的作用域。闭包:引用了自由变量的函数。

function f(){
	var x = 100;
	function g(){
		...
	}
	g();
}
f();

function f(){//f()创建的时候(浏览器解析的时候),给f添加一个看不到摸不着的成员[[scope]]
 		  	 //f.[[scope]] == window
			 //f()被调用的时候会创建自己的一个词法环境f.le, f.le ->f.[[scope]]
//在预处理阶段(上一行解释,f()刚刚被调用时),f.le{x = undefined, g(){...}},g.[[scope]] == f.le
//在预处理阶段,无论g()是函数声明还是函数表达式创建的都会把g()加入到f.le中
var x = 100;//在执行阶段,f.le{x = 100, g(){...}}
function g(){//在预处理阶段,g.[[scope]] == f.le
...
}
g();//g()在运行时,会创建自己的词法环境,g.le ->g.[[scope]]
	//综上形成一条链条
	//g.le -> g.[[scope]] == f.le -> f.[[scope]] == window
}
f();

JS创建函数的方式

有3种:

1.函数声明

function f() {…}

2.函数表达式

var f = function () {…}

3.Function构造函数

var f = new Function(“参数”,”函数体”)//这种方式创建的作用域永远指向全局,而不是它的父

闭包

,是词法闭包的简称,是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而来的实体。

function f1() {
	var a = 10;
	var b = 20;
	function f2() {//f2()函数就是闭包
		console.log(a);
	}
	f2();
}
f1();

闭包的好处

1.减少全局变量

//达到每次调用函数add(),a都不断加1

function add(){
	var a = 0;
	a++;
	alert(a);
}
add();
add();
//这样做每次调用a都从0开始
//这样可以实现累加效果
var a = 0;
function add() {
	a++;
	alert(a);
}
add();
add();
//但这样做就增加全局变量
//改为闭包的方式
function f(){
	var a = 0;
	return function(){
		a++;
	alert(a);
	}
}
var result = f();
result();
result();

2.减少传递给函数的参数数量

//实现例如基数为2,最大数为3,返回值为2+ 1+2+3的值

//可以这样实现

function add(base,max){…}

//用闭包
function calFactory(base){
	return function(max){
		var total = 0;
		for(var i = 1, i <= max, i++ ){
			total +=i;
		}
		return total + base;
	}
}
var adder = calFactory(2);
adder(3);//8//2+1+2+3
adder(4);//12//2+1+2+3+4

3.起到封装的作用

(function(){
	var m = 0;
	function getM(){//闭包
		return m;
	} 
	function setM(){//闭包
		m = val;
	}
	window.g = getM;//通向外部的接口
	window.s = setM;
})();//立即调用的匿名函数
s(12);
alert(g());

闭包的注意事项:

1.对于捕获到的变量只是个饮用,不是复制

function f(){
	var num = 1;
	function g(){
		alert(num);//引用父的num
	}
	num++;
	g();//2
}

2.父函数每调用一次,会产生不同的闭包

function f(){
	var num = 1;
	return function(){
		num++;
		alert(num);
	}
}
var result1 = f();//由函数的预处理可知,每次调用一个函数的时候会创建一个新的词法环境
result1();//2
result1();//3
var result2 = f();//创建一个新的词法环境
result2();//2
result2();//3

3.循环中的问题

<body>
<div id = “1”>1</div>
<div id = “2”>2</div>
<div id = “3”>3</div>
for(var i = 1;i <=3;i++){
	var ele = document.getElementById(”i”);
	ele.onclick = function(){
		alert(i);
	}
}
</body>
//点击1 2 3 都会弹出4,因为没有块级作用域,所以i相当于全局变量
//解决办法,可以用闭包
<body>
<div id = “1”>1</div>
<div id = “2”>2</div>
<div id = “3”>3</div>
(for(var i = 1;i <=3;i++){
	var ele = document.getElementById(”i”);
	ele.onclick = (function(id){
		return function(){
		alert(id);
	}
})(i);//每次点击1或2或3都会立即执行函数,从而达到点击几弹出几的效果
//每被调用一次函数,父函数的i都会被捕获,都会重新产生一个闭包
}
</body>

伪类

对象的_ _ proto _ _与函数无关,直接指向函数的prototype

//函数有prototype属性,对象有_ _ proto _ _属性,函数也是对象,所以函数也有prototype属性

function Person(){
	var age = 30;
	this.age = 22;
	this.name = “nice”;
}
Person.prototype.headCount = 1;
var p = new Person();
		//此时Person()相当于伪类,对象p内部有age和name属性,p也可通过__proto__指向
		//Person.protptype,p完全与Person()无关了。
Person.prototype = {xx:”xx”};//将Person的prototype完全改写
console.log(p.xx);//undefined//此时p已经不能取到新的Person.prototype了
console.log(p.headcount);//1//但是原来的p.__proto__指向的Person.prototype还存在着

this

this永远指向对象

this可以直接写在全局里面,并且此时永远指向window对象;this也可以写在函数里面,此时氛围三种情况:

第一种情况:

var ele = document.getElementById(”id”);
ele.addElementListener(”click”,function{
	console.log(this);//这种情况this永远指向引发事件的对象ele
})

第二种情况:

//this的指向是运行时决定,不是编写代码时决定

//运行时函数是谁调用的,this就指向谁

var o = {
	name:”nice”;
	print:function(){
		console.log(this.name);
	}
}
o.print();//cj//函数是o调用的
var ff = o.print;
ff();//undefined//此时相当于window.ff(),函数是window调用的

第三种情况:

function f(){
	this.name = “nice”;
}

上面第二种情况改变了this的指向,还可以使用call或apply或bind改变this指向,call和apply和bind都是函数对象都会有的东西

call和apply的区别:

传参的时候call使用参数列表的形式,一个一个地传,而apply使用参数数组的形式

var o = {
	name:”nice”;
	print:function(){
		console.log(this.name);
	}
}
var ff = o.print;
ff();//undefined
this.name = “global”;
this.age = 22;
ff();//global
function log(a,b){
	console.log(this[a]);
	console.log(this[b]);
}
log(”name”,”age”);//global 22
log.call(o,”name”);//nice//改变了this的指向,相当于o.log(“name”)
log.call(o,”name”,”age”);//nice undefined//undefined因为在o对象中没有age变量
log.apply(o,[’’name”,”age”])//nice undefined

new自定义的实现

function Person(name, age){
	this.name = name;
	this.age = age;
}
var p1 = new Person(”cj”, 22);
console.log(p1.name);//“cj”
console.log(p1.age);//22

实现编写一个上例中的new

function New(f){//f是一个构造函数
	return function(){//return的这个函数相当于上面的new Person()	
		var o = {”__proto__”:f.prototype};	
		f.apply(o,arguments);	
		return o; 	
	}
}
var p2 = New(Person)(”xx”,33);
console.log(p2.name);//“xx”
console.log(p2.age);//33
console.log(p2 instanceof Person);//true
Person.prototype.xx = “aaaa”;
console.log(p2.xx);//“aaaa”

封装

function Person (){
	var age = 100;//私有成员,外界访问不了
	function pm(){//私有成员
		console.log(”private method”);
	}
	this.name = name;//公有成员
	this.text = function(){//公有成员
		console.log(”public method”);
	}
}

//上面的代码这样写就没有意义了,应该向下面这样写才有意义

function Person (){
	var age = 100;
	function pm(){
		console.log(this.name);
	}
	this.name = name;//公有成员
	this.text = function(){//公有成员
		console.log(”public method”);
		pm();
	}
}
var p1 = new Person(”cj”);
console.log(p1.text());//public method undefined
//因为pm()前面没前缀,所以调用window中的pm(),为undefined
//将代码pm()变换为pm.call(this)将this的指向为Person

//或者用对象工厂的方式解决公有私有的互相调用

function Person(pname){
	function pm(){
		console.log(self.name);
	}
	var self = {
		name:pname;
		test:function(){
			pm();
		}
	};
	return self;
}
var p2 = Person(”cj”);
console.log(p2.test());//“cj”

继承

浅拷贝

var person = {
	name:”cj”;
	age:22;
}
var programmer = {
	language:”Javascript”;
}
function extend(p, c){
	var c = c||{};
	for(var prop in p){
		c[prop] = p[prop];
	}
	return c;//没有这个语句也可以,没有的话传回的是个引用
}
extend(person, programmer)
//此时programmer便继承了person的属性
var person = {
	name:”cj”;
	age:22;
	address:{
		home:”home address”;
		office:”office address”;
	}
}
var programmer = {
	language:”Javascript”;
}
function extend(p, c){
	var c = c||{};
	for(var prop in p){
		c[prop] = p[prop];
	}
	return c;//没有这个语句也可以,没有的话传回的是个引用
}

extend(person, programmer)
console.log(programmer.name);//“cj”
programmer.name = “nice”;
console.log(person.name);//“cj”//浅拷贝,变量值改变
console.log(programmer.name);//“nice”
programmer.address.home = “Shenzhen”;
console.log(programmer.address.home);//“Shenzhen”
console.log(person.address.home);//“Shenzhen”//子改变了父的值,说明只是浅拷贝
//子和父的引用指向同一个位置
//数组和对象一样,可存在这个问题
//数组也可以用for in循环遍历

深拷贝

对象自变量的形式实现的继承

function extendDeeply(p, c){
	var c =c||{};
	for(var prop in p){
		if(typrof p[prop] === “object”){
			c[prop] = (p.[prop].constructor === Array)?[]:{};
			extendDeeply(p[prop], c[prop]);
		}else{
			c[prop] = p[prop];
		}	
	}
}

用构造函数的形式实现继承

function Parent(){//父对象
	this.name = “abc”;
	this.address = {home:”home”};
}
function Child(){//子对象
	Parent.call(this);//用call形式拷贝
	this.language = “js”;
}
c = new Child();
console.log(c.name);//“abc”
P = new Parent();
c.address.home = “Shenzhen”
console.log(p.address.home);//“home”//是深拷贝

create实现继承

var p = {name:”cj”};//p.__proto__指向object.prototype,object.prototype.__proto__指向null
function myCreate(p){
	var ins;
	function F(){};
	F.prototype = p;
	ins = new F();
	return ins;
} 
var c = myCreate(p);//系统自带的Object.create(p);
console.log(c.name);//“cj”

上面的代码有系统自带的Object.create()

还可以进行属性的增强,例如

var c2 = Object.create(p,{age:{value:20},salary:{value:1000000}});//p是父类继承的,
																  //后面是子类增强的
console.log(name);//“cj”
console.log(age);//20

再谈instanceof

左边是对象,右边是函数

var f = new F();
console.log(f instanceof F);//true//判断时相当于判断是否f.constructor == F,是对的
console.log(f instanceof Object);//true
//很显然像上面一样判断是否f.constructor == Object不成立
//但是上面的那个成立,所以还可以进一步判断
//是否F.prototype.__proto__== Object.prototype,成立

类的继承

//创建父类

//创建子类

//建立关系

第一种方式

function P(){}//父类
function C(){}//子类
function P(){}//父类
function C(){}//子类
C.prototype = P.prototype//此时C.prototype.constructor 是P(),所以用类的方法继承后,
//要将原型类型的构造器恢复回来C.prototype = C;
var c1 = new C();
console.log(c1 instanceof C);//true
console.log(c1 instanceof P);//true
C.prototype.xx = “xx”;
var p1 = new P();
console.log(p.xx);//“xx”
//不要用这种方式,因为缺点是子继承父,但是子的东西同样暴露给了父

第二种方式

function P(){}//父类
function C(){}//子类
C.prototype = new P();
var c1 = new C();
console.log(c1 instanceof C);//true
console.log(c1 instanceof P);//true
C.prototype.xx = “xx”;
var p1 = new P();
console.log(p.xx);//undefined
//优点:子的东西不会暴露给父
//缺点:在查找时中间多了一层,而且凭空new个P没有什么用,只是起到子与父的桥接

第三种形式

function P(){}//父类
function C(){}//子类
function F();
F.prototype = P.prototype;
var f = new F();
C.prototype = f;
//整体代码实际就是上面写过的Object.create(),所以可以直接写成
C.prototype = Object.create(P.prototype);
//优点是:只是中间new了一个空函数,不会有像上面所说的缺点

类的继承四部曲:

1创建父类以及添加父类的属性和方法

2创建子类

3立即继承

上面实现了继承,但是应该本能地将子类原型的constructor修正回子类的构造函数

4此时再添加子类的属性,

加入3和4颠倒,继承的时候有等号,相当于完完全全把子类重写成了父类的样子

//举例

function Person(){}//创建父类
Person.prototype.headCount = 1;//添加父类属性
Person.prototype.eat = function(){//添加父类方法
	console.log(”eating...”);
}
function Programmer(){}//创建子类
Programmer.prototype = Object.create(Person.prototype);//继承a
Programmer.prototype.constructor = Programmer;//将子类原型的constructor修正回来(本能)a
Programmer.prototype.language = “Javascript”;//添加子类属性
Programmer.prototype.work = function(){//添加子类方法
	console.log(”i am writing code in” + this.language);
}
var js = new Programmer();
console.log(js.eat());//eating...
console.log(js.language);//“Javascript”
js.language = “JS”;
console.log(js.language);//“JS”
console.log(js.work());//i am writing code in JS

还可以将上面即成的代码(注释里有a)改为下面的代码

createEx(Programmer, Person);
......
function createEx(Child, Parent){
	function F(){};
	F.prototype = P.prototype;
	Child.prototype = new F();
	Child.prototype.constructor = Child;
}
function Person(name, age){//创建父类
	this.name = name;//父类有实例级别的成员,才写aaaa行代码
	this.age = age;
}
Person.prototype.headCount = 1;//添加父类属性
Person.prototype.eat = function(){//添加父类方法
	console.log(”eating...”);
}
function Programmer(name, age, title){//创建子类
	Person.apply(this, arguments);//把父类实例级别的成员继承了过来aaaa
}
createEx(Programmer, Person);
Programmer.prototype.language = “Javascript”;//添加子类属性
Programmer.prototype.work = function(){//添加子类方法
	console.log(”i am writing code in” + this.language);
}
function createEx(Child, Parent){
	function F(){};
	F.prototype = P.prototype;
	Child.prototype = new F();
	Child.prototype.constructor = Child;
}
var person = new Person(“cj”, 20);
console.log(person.name);//”cj”
var p = new Programmer(”nice”, 22, “coder”);
console.log(p.name);//“nice”
person.name = “haha”
console.log(”p.name”);//“nice”//没有影响

有时我们会在子里面调用父的东西,用super或base

function Person(name, age){//创建父类
	this.name = name;
	this.age = age;
}
Person.prototype.headCount = 1;//添加父类属性
Person.prototype.eat = function(){//添加父类方法
	console.log(”eating...”);
}
function Programmer(name, age, title){//创建子类
	Person.apply(this, arguments);//把父类实例级别的成员继承了过来
}
createEx(Programmer, Person);
Programmer.prototype.language = “Javascript”;//添加子类属性
Programmer.prototype.work = function(){//添加子类方法
	console.log(”i am writing code in” + this.language);
	Programmer.base.eat();//****新加的语句,使得子调用了父的方法
}
function createEx(Child, Parent){
	function F(){};
	F.prototype = P.prototype;
	Child.prototype = new F();
	Child.prototype.constructor = Child;
	Child.super = Child.base = Parent.prototype;//****新加的语句
}
var csharp = new(”cj”, 22, “teacher”);
console.log(csharp.name);//“cj”
console.log(csharp.work());//i am writing code in Javascript
 						   //eating...

多态

拥有

function F(){};
var f = new F();
f.name = “cj”;
console.log(f.hasOwnProperty(”name”));//true//是否有
F.prototype.age = 22;
console.log(f.hasOwnProperty(”age”));//false//f对象和原型对象是两个对象,不存在拥有
console.log(Object.hasOwnProperty(f, ”name”));//false
//不要这么用,因为是对象级别的hasOwnPorperty
console.log(F.prototype.isPrototypeOf(f));//true//是否是...的原型
console.log(Object.getPrototypeOf(f));//F{}//找f的原型

方法重载

编译时的多态,典型的代表是方法重载(大概意思就是调用一个参数时,就是一个参数的//A方法,调用两个参数时就是两个参数的A方法),编译时就确定了

JS中不允许同名的方法,后者会覆盖前者

arguments,实参的个数,在函数里才会有这个对象,是类似数组的对象

fucntion demo(a, b){
	console.log(demo.length);//形参的个数
	console.log(arguments.length);//实参的个数
}

可变长度的重载

function add(){
	var total = 0;
	for(var i = arguments.length - 1; i >= 0; i—){
		total += arguments[i];
	}
	return total;
}
console.log(add(1));//1
console.log(add(1, 2));//3

参数的个数不一样

function fontSize(){
	var ele = document.getElementById(”js”);
	if(arguments.length == 0){
		return ele.style.fontSize;
	}else{
		ele.style.fontSize = arguments[0];
	}
}
fontSize(18);
console.log(fontSize());//18

参数类型不一样

function setting(){
	var ele = document.getElementById(”js”);
	if(typeof arguments[0] === “object”){
		for(p in arguments[0]){
		ele.style[p] = arguments[0][p];
	}else{
		ele.style.fontSize = arguments[0];
		ele.style.backgroundColor = arguments[1];
	}
}
setting(18, “red”);
//setting({fontSize:28, backgroundColor:”green”});

方法重写

运行时多态:重写

下面的例子算一种

function demo(o){
	o.run();
}
var o = {run:function(){
	console.log(”o is running...”);
}};
demo(o);//o is running...
var p = {run:function(){
console.log(”p is running...”);
}};
demo(p);//p is running...
//依据原型链的特点,在实例上重写一个与构造函数一样的方法,实例使用自己的方法
var f = new F();
F.prototype.run = function(){
	coslole.log(”FFF”);
}
f.run();//FFF
f.run = function(){//重写
	console.log(”fff”);
}
f.run();//fff
		//重写的过程中还可以调用父的东西
f.run = function(){
	console.log(”ffff”);
	F.prototype.run();
}
f.run();//ffff
		//FFF

其他情况

function Parent(){
	this.run = function(){
		console.log(”parent is running”);
	}
}
function Child(){
	Parent.call(this);//继承父类
	var parentRun = this.run;//保留父的方法的引用,看看以后是否需要调用父的方法
	this.run = function(){//子类重写run方法
		console.log(”child is running”);
		parentRun();//调用父刚刚记录的东西
	}
}
var c = new Child();
c.run();//child is running
		//parent is running

其他情况

function Parent(){}
Parent.prototype.run = function(){
	console.log(”parent is running”);
}
Child.prototype = Object.create(Parent.prototype);//继承
Child.super = Parent.prototype;//把父的东西弄过来
function Child(){}
Child.prototype.run = function(){
	console.log(”child is running”);
	Child.super.run();//调用父的东西
}
var c = new Child();
c.run();//child is running
		//parent is running
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值