JavaScript程序设计进阶篇

类型


所有用 new 关键字 加定义函数 构造的都是引用类型。

对象类型


  • 浏览器扩展对象

浏览器扩展对象,是各个浏览器生产厂商为自己的浏览器扩展的js对象。

随着w3c,等规范,浏览器扩展对象,正在逐步消失

  • 宿主对象

宿主对象是浏览器运行环境所提供的对象,window document navigator等对象。

开发者通过这些对象,操作浏览器中的节点。

  • 原生对象

ecmi规范定义的一一系列对象。 构造函数和对象

  • 原始类型和对象类型的区别

引用类型,在栈内存中存入地址,地址指向堆内存的对象,实际值在堆内存中。

值类型的变量存在栈内存中。

值类型的赋值是单纯对值的复制。

引用类型的赋值,是将地址赋值给另一个标识符,而地址的指向不变,指向同一个内容。

隐式类型转换

那些场景会进行隐式类型转换:

  • 数字运算符
  • .
  • if 语句
  • ==

显示类型转换的方法

  • Number(), String(), Boolean()
  • parseInt(), parseFloat()
  • !,!!

类型识别


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/* 
* 输入格式:
* '2015-08-05'
* 1438744815232
* {y:2015,m:8,d:5}
* [2015,8,5]
* 返回格式 Date
*/
function toDate(param){
if (typeof(param) == 'string' ||
typeof(param) == 'number') {
return new Date(param);
}
if (param instanceof Array) { // 判断参数是否是数组
var date = new Date(0);
date.setYear(param[0]);
date.setMonth(param[1] - 1);
date.setDate(param[2]);
return date;
}
if (typeof(param) == 'object') {
var date = new Date(0);
date.setYear(param.y);
date.setMonth(param.m - 1);
date.setDate(param.d);
return date;
}
return -1;
}

类型识别方法:

  • typeof(操作符)
  • instanceof
  • Object.prototype.toString.call
  • constructor

typeof

typeof 可以识别标准类型,除了Null以外

不能识别具体的对象类型

instanceof

Object.prototype.toString.call

1
2
3
4
5
Object.prototype.toString.call(123)
// "[object Number]"

Object.prototype.toString.call("123")
// "[object String]"

并不能识别自建对象的具体类型

constructor

获取对象构造函数名称

1
2
3
4
5
6
7
function getConstructorName(obj){
return (obj===undefined||obj===null)?obj:(obj.constructor&&obj.constructor.toString().match(/function\s*([^(]*)/)[1]);
}

getConstructorName(null) // null
getConstructorName(undefined) // undefined
getConstructorName(new Date()) // "Date"

(function(){}).constructor === Function
true

函数


函数声明定义函数的特点:

1
2
3
4
5
6
7
8
9
10
11
12
add1(1);
function add1(i){
console.log("函数表达式:"+(i+1));
}

// add2(1);
var add2 = function(i){
console.log("函数表达式:" + (i+2));
}

add3(3);
var add3 = new Function("i","console.log('函数实例化:' + (i+3));");

只有第一种方式定义的函数可以在函数定义之前调用

代码执行顺序如下:

函数声明定义函数时,如果重复定义,在代码执行中,后面定义的函数会将前面的函数实现覆盖。

而其他定义方式则都是有效的

总结:用函数声明定义函数的特点


  1. 函数定义会被前置到代码顶部
  2. 重复定义函数时,只有最后一次定义有效

函数实例化定义函数特点:定义的函数只能访问本地作用域和全局作用域。

函数调用


函数调用模式:

  • add(1)
  • 对象.方法()调用
  • 构造函数调用 new Function(…);
  • apply(call)调用模式

Function.prototype.apply的使用

apply方法:

1
2
3
4
5
6
语法:apply([thisObj[,argArray]]) 
定义:应用某一对象的一个方法,用另一个对象替换当前对象。
说明:
如果 argArray 不是一个有效的数组或者不是 arguments 对象,那么将导致一个 TypeError。
如果没有提供 argArray 和 thisObj 任何一个参数,
那么 Global 对象将被用作 thisObj, 并且无法被传递任何参数。

apply实际上是一种对象方法的借用机制。例如:

circle没有改变圆心的方法,于是

p.move.apply(cirle, [2, 1]) // {x: 3, y: 2, r: 1}

circle对象借用 p.move方法

函数调用模式的区别

虽然有很多种函数调用模式,但归根到底,电脑执行的还是一个函数。函数执行时,JS引擎会在函数自己的本地作用域上自动添加 this, arguments这两个临时变量,这些调用模式的本质区别就体现在this这个变量的指向上。

  • 函数调用模式:this指向全局对象
  • 方法调用模式:this指向方法调用者
  • 构造函数调用模式下:this指向被构造的对象
  • apply(call)调用模式:this指向第一个参数
1
2
3
4
5
6
7
8
9
10
11
12
13
var myNumber = {
value : 1,
add: function(i){
var helper = function(i){
console.log(this);
this.value += i;
}
helper(i);
}
}

myNumber.add(1);
console.log(myNumber.value);

helper函数中this指向window对象,这段代码无法使实现正确逻辑,

函数传参


  • 值类型参数按值传递 - call by value

函数的形参是被调用时所传实参的副本。修改形参的值并不会影响实参。

JS原始类型的变量采用的是按值传递的方式

  • 对象类型参数按共享传递 - call by sharing

  • 数按引用传递 - call by reference

First-class function


(函数即变量,作为参数,作为返回值)

function.prototype.bind


1
2
3
4
5
6
7
8
9
10
11
12
13
14
function Point(x, y){
this.x = x;
this.y = y;
}

Point.prototype.move = function(x, y) {
this.x += x;
this.y += y;
}

var p = new Point(0, 0);
var circle = {x: 1, y: 1, r: 1};
var circlemove = p.move.bind(circle, 2, 1);
circlemove(); // 调用这个函数,circle 才会移动。
1
2
3
4
5
6
7
8
9
10
11
var move = function(x, y){
this.x += x;
this.y += y;
}
var p = {x: 1, y: 1};

var pmove0 = move.bind(p);
pmove0(1, 2);

var pmove1 = move.bind(p, 1);
pmove1(2);

bind 时,不需要传递参数,只给move指定调用者

bind返回的是一个函数引用。

函数作为返回值

1
2
3
4
str = str.replace(/\d/g, (function(){
var index = 0;
return arr[index++];
})());

思考题:


对象方法中定义的子函数,子函数执行时 this 指向哪里?

  1. 以下代码中打印的this是个什么对象?
  2. 这段代码能否实现使 myNumber.value 加1 的功能?
  3. 不放弃helper函数的前提下,有哪些修改方法可以实现正确的功能?
1
2
3
4
5
6
7
8
9
10
11
var myNumber = {
value: 1,
add: function(i){
var helper = function(i){
console.log(this); // window
this.value += i;
}
helper(i);
}
}
myNumber.add(1);

2. 不能

3. 不放弃helper函数的前提下,有哪些修改方法可以实现正确的功能?

第一种方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
var myNumber = {
value : 1,
add: function(i){
var helper = function(i, obj){
console.log(this);
obj.value += i;
}
helper(i, this);
}
}

myNumber.add(1);
console.log(myNumber.value);

第二种方法

1
2
3
4
5
6
7
8
9
10
11
12
var myNumber = {
value : 1,
add: function(i) {
var helper = function(i){
console.log(this);
this.value += i;
}
helper.apply(this, [i]);
}
}
myNumber.add(2);
console.log(myNumber.value);

方法三:

1
2
3
4
5
6
7
8
9
10
11
12
13
var myNumber = {
value : 1,
add: function(i){
var helper = function(i, obj){
console.log(this);
this.value += i;
}
helper.call(this, i);
}
}

myNumber.add(1);
console.log(myNumber.value);

方法四:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var myNumber = {
value : 1,
add: function(i){
var helper = function(i, obj){
console.log(this);
this.value += i;
}
var add = helper.bind(this, i);
add();
}
}

myNumber.add(1);
console.log(myNumber.value);

原型

—

主要内容:原型,设置对象的原型,原型链

原型 vs 类

类: 抽象 -> 具体

原型:具体 -> 具体 通过一个现有的对象构造一个新的对象

Object.create(proto[,])


proto 一个对象,作为新对象的原型

例子:

1
2


_proto_ 属性指明其原型,隐式属性,不能够被直接修改。

构造函数设置对象原型


使用prototype设置原型,使用new创建对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function Car(logo) {
this.logo = logo || 'unknown name';
}

Car.prototype = {
start: function() {
console.log('%s start', this.logo);
},
run: function(){
console.log('%s run', this.logo);
},
stop: function(){
console.log('%s stop', this.logo);
}
}

var landRover = new Car('landRover');
var landWind = new Car('landWind');

landRover.start();

大家共享 prototype属性,

原型链


对象属性的访问修改和删除


在访问属性时,和OC一样,会在对象中先查找该属性,如果对象中没有则会顺着原型链,proto 往上查找其原型中是否有该属性,一直向上。找到后则使用当前原型的属性或方法。

而修改和删除,都只能修改和删除对象自身的属性和方法,而不会影响到原型链中的属性和方法。

如果这时,要修改 landRover1.logo = "2";

则会在landRover1中创建一个 logo属性并赋值 “2”;

如果 landRover1.name = ‘myCar’;

也会在landRover1中创建一个 name 属性,并赋值。

delete landRover1.logo 不会删除原型上的logo,landRover1 中的logo已经删除。 这时访问landRover1中的logo属性,则会显示undefined

hasOwnProperty

判断传入的属性是否是对象自身的属性。

函数在js中也是对象

LandRover Car 甚至 Function 这些函数,其实都有原型链。

LandRover Car 是通过 new Function 创建出来的,而 Function 其实也是通过 new Object 创建出来的。

变量作用域


生命周期, 作用范围

静态作用域:由程序定义的位置决定的,在编译阶段决定

动态作用域


在程序运行时决定。

在栈里面找离栈顶最近的X

JS 变量作用域


Js 使用静态作用域

Js没有块级作用域(全局作用域、函数作用域)

词法环境结构:形参 函数声明 变量… arguments 还有一个队外部环境的引用 outer

环境记录初始化

声明提前,在全局代码初始化之前,初始化一个词法环境

语法环境 with try catch

with语句会创建一个临时的词法环境。

f函数是函数声明,其outer为全局语法环境,下面的function()函数表达式,函数表达式在执行时到该代码时,才会规定语法环境。

在with(try/catch)中定义的函数声明,其外部的词法环境的引用还是和没有with(try/catch)时一样,不能访问特殊词法环境中的形参等。

闭包


什么是闭包?

  • 函数内部定义的子函数,用到了父函数的变量形成的作用域。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
function sum(i, j){
var add = function(i, j){
return i + j;
}
return add(i, j);
}

var startTime = new Date();

for(var i = 0; i < 1000000; i++){
sum(1, 1);
}
var endTime = new Date();
console.log(endTime - startTime);


var sum = (function(){
var add = function(i ,j){
return i + j;
}
return function(i, j){
add(i, j);
}
})()
var startTime = new Date();
for (var i = 0; i < 1000000; i++) {
sum(1, 1);
}
var endTime = new Date();
console.log(endTime - startTime);
1
2
3
4
5
6
7
8
9
10
function add(){
var i = 0;
return function() {
alert(i++);
}
}

var f = add();
f();
f();

add()函数执行完后,变量i并不会被释放,原因是f 指向匿名函数中,还引用这i这个变量,因此,i不会被释放。

闭包由函数和相关的引用环境组合而成。闭包允许函数引用环境中的变量。(又称自由变量)

广义上说,所有的JS的函数都可以称为闭包,因为JS函数在创建时保存了当前的词法环境。

闭包的应用


保存现场、封装

保存现场

小案例

需要实现上面的方法, addlanders(nodes) 点每一行 则弹出 该行前面的数字。

1
2
3
4
5
6
7
function addHandlers(nodes){
for (var i = 0; i < nodes.length; i++) {
nodes[i].onclick = function(){
alert(i);
}
}
}

这个方法表示给每一个node绑定一个响应事件,而在该方法中,不管点击哪行,i始终等于 nodes.lenth。

这些匿名函数的outer是 addHandlers ,因此这些函数在执行的时候,会在 addHandlers 中找变量 i 。

因此不能满足要求,这里可以使用闭包的保存引用的值的特性:

1
2
3
4
5
6
7
8
9
10
function addHandlers(nodes) {
function helper(i) {
return function() {
alert(i);
}
}
for (var i = 0; i < nodes.length; i++) {
nodes[i].onclick = helper(i);
}
}

如果这样写,则匿名函数 function() 的outer 是helper 所以会把当前的 i 保存下来。
调用时,会调用保存下来的i。

使用闭包封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var observer = (function() {
var observerList = [];
return {
add: function(obj) {
observerList.push(obj);
},
empty: function() {
observerList = [];
},
getCount: function() {
return observerList.length;
},
get: function() {
return observerList;
};
}
})();

使用闭包封装,不会暴露内部变量,而只暴露接口。内部方法共享一个变量。

闭包的使用举例:

1
2
3
4
// 将字符串中的一些特定字符按顺序用数组中的元素替换,例如:
var arr = ['c','f','h','o'];
var str = 'ab4de8g4ijklmn7';
// 替换后 str == 'abcdefghijklmno';

bind 低版本兼容问题


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/* 
在ECMA-262第五版规范中,函数的bind方法创建并返回一个新的方法,当这个新方法执行时,它的this值就是bind方法接收的第一个参数,bind方法接收的剩余参数依次作为新方法执行时的传入参数。其调用语法如下:
*/

function.bind(thisArg[, arg1[, arg2[,…]]])

// (fun为定义好的函数)
// 下面是bind方法的一个使用场景:

var foo={
x:10,
add:function(y){
alert(this.x+’+’+y+’=’+(this.x+y));
},
addAsyn:function(y){
window.setTimeout(this.add.bind(this,y),10);
}
}
foo.addAsyn(5);

// bind方法在低版本浏览器中不支持,请给出一种兼容低版本浏览器的代码实现。
1
2
3
4
5
6
7
8
9
10
11
12
if (!Function.prototype.bind) {//如果原型上没有bind方法
Function.prototype.bind = function (context) {
var args = arguments,//获取要传入的所有参数
obj = arguments[0],//获取要绑定的上下文
func = this;//获取要调用的函数
return function(){//返回一个新的函数
var argc = Array.prototype.slice.call(args,1);//获取bind的剩余传入参数
Array.prototype.push.apply(argc,arguments);//将调用时的参数放到最后
return func.apply(obj||null,argc);//使用新的this执行func函数
}
}
}

面向对象


全局变量、封装、继承

var test = ‘’;

window.test = ‘’;

test = 5; // 隐式的
console.log(window.test);

后两种相当于将变量定义到 window 对象上,如果使用 delete 可以删除。,第一种不可删除。

例子:

1
2
3
function todo() {
var a = test = '';
}

上面这段程序,只定义了 a

信息隐藏:该暴露,该隐藏。

封装


private 私有

protected 对象自身或者派生出来的对象都可以访问

js 的封装:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function A() {
this.a = null;
this.b = null;

this.step1 = function() {

}

this.step2 = function() {

}

this.api = function() {

}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function A() {
this.a = null;
this.b = null;
}

var pro = A.prototype;

pro.step1 = function() {

}

pro.step2 = function() {

}

pro.api = function() {

}

A() 这样一个类,然后在A这个原型上添加一些方法。

上述方法中,都有个共同问题,外部可以全部访问到内部变量。

要做到信息隐藏:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function A() {
var _config = ['A', 'B', 'C'];
this.getConfig = function() {
return _config;
}
}

var pro = A.prototype;
pro._step1 = function() {

}
pro._step2 = function() {

}
pro.api = function() {

}

_config属性就是私有的,仅仅在A的函数里面才能访问。

继承


两种类型的继承

类继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
(funtion() {
function ClassA() {}
ClassA.classMethod = function(){}
ClassA.prototype.api = function(){}

function ClassB() {
ClassA.apply(this, arguments);
}

ClassB.prototype = new ClassA();
ClassB.prototype.constructor = ClassB;
ClassB.prototype.api = function() {
ClassA.prototype.api.apply(this, arguments);
}
// 创建 ClassB 对象,A是父类,B是子类
var b = new ClassB();
b.api();

})();

继承结构:

原型继承

1
2
3
4
5
6
7
8
9
10
11
(function() {
var proto = {
action1: function() {

},
action2: function() {

}
}
var obj = Object.create(proto);
})();

如果要兼容低版本:

1
2
3
4
5
6
7
var clone = (function() {
var F = function() {};
return function(proto) {
F.prototype = proto;
return new F();
}
})();

继承:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
function Circle(radius) {
this.radius = radius;
}

Circle.prototype = {
perimeter: function() {
return 2 * Math.PI * (this.radius);
},
area: function() {
return Math.PI * (this.radius) * (this.radius);
},
};

// var c = new Circle(2);
// console.log(c.perimeter());
// console.log(c.area());

function ClassB(radius, point) {
Circle.apply(this, arguments);
this.point = point;
}

ClassB.prototype = new Circle();
ClassB.prototype.constructor = ClassB;

ClassB.prototype.getPoint = function() {
console.log(this.point);
}

var kid = new ClassB(2, 12);
console.log(kid.area());
console.log(kid.perimeter());
kid.getPoint();
文章作者: Ammar
文章链接: http://lizhaoloveit.cn/2016/08/05/JavaScript%E7%A8%8B%E5%BA%8F%E8%AE%BE%E8%AE%A1%E8%BF%9B%E9%98%B6%E7%AF%87/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Ammar's Blog
打赏
  • 微信
  • 支付宝

评论