您的位置:首页 > Web前端 > JavaScript

3-js面向对象基础 原型链进阶

2016-10-31 00:18 615 查看

对象的原型链

凡是对象都有原型

构造函数 Person 创建的对象 实例 p 有原型 => Person.prototype 或 p.__proto__

Person.prototype 是对象实例, 所以他也有原型 => Person.prototype.__proto__

问题: 原型是什么? 原型是对象。那对象又有原型,如何是个头?

function Person() {
// 一旦定义函数 就有了两个部分 构造函数 神秘对象(原型)
// Person.prototype

var p = new Person();
// p 默认连接到 Person.prototype 上
// 简单的表示为 p -- > p.__proto__
console.log( p.__proto__ === Person.prototype );//true

// Person.prototype  Person对象的原型
// Person.prototype.__proto__ -->  Object.prototype, Person对象的原型的原型, 就是构造函数Object的原型
// Object.prototype.__proto__ -->  null     构造函数Object的原型的 原型
}


结论:

Person.prototype 是 实例 p 的原型对象, p 对象 使用 __proto__ 可以访问原型对象

Person.prototype 的 原型对象是 Person.prototype.__proto__

Person.prototype.__proto__ 里的 constructor属性 是 Object, 所以Person.prototype.__proto__ 就是 Object.prototype

Object.prototype.__proto__ 是 null. 因此表明 Object.prototype 就是顶级.

链式

p –> Person.prototype( p.__proto__ ) –> Object.prototype –> null

Person的原型链结构



系统内置的原型链

[] –> Array.prototype –> Object.prototype –> null

/./ –> RegExp.prototype –> Object.prototype –> null

… …

{} 对象的原型链结构

在 js 中 对象 一般都有字面量

123, ‘123’

数组: []

正则表达式对象: /./

函数: function () {}



对象也有字面量: { },对象的原型链结构

{ } –> Object.prototype –> null

注意: {} 与 new Object() 含义相同,相当于用Object构造函数构造出来的实例

数组的原型结构图



继承原型链结构图

function Person( name, age, gender ) {
this.name = name;
this.age = age;
this.gender = gender;
}
function Student () {}

Student.prototype = new Person( '张三', 19, '男' );

var stu = new Student();
console.log( stu.constructor ); // Person构造函数  详解:自己没有,去原型找,原型是Person实例,Person对象实例也没有,去实例.__proto__找,就是在 Person.prototype 找,找到了,所以是 Person构造函数
console.log( stu.name );    // 张三  自己没有name属性,去原型找,原型是Person实例,Person实例有 name,找到了,所以是 张三


继承原型链的图



动态函数 Function

动态函数就是在运行的过程中, 将一段字符串作为代码运行.

由于字符串可以随意的拼接. 因此得到动态的执行.

定义动态函数, 并执行

使用 Function 构造函数, 创建函数对象

Function 是一个构造函数. new Function 得到 一个函数

语法:

new Function( arg0, arg1, …, argN, body )

Function 的所有的参数, 除了最后一个以外, 都是生成的函数的参数,最后一个参数是 函数体

/* 求两数和 */
function getSum( num1, num2 ) {
return num1 + num2;
}
/* 使用 new Function */
var getSum2 = new Function( 'num1', 'num2', 'return num1 + num2');
console.log( getSum2( 1, 2 ) ); // 3
console.log( getSum( 1, 2 ) );  // 3


由于实例化 Function对象,传的参数是字符串,所以我们可以实现动态执行的效果

var demo = prompt("请输入你要执行的代码");  // 此处输入 alert(num1 * num2);
var fuc = new Function( 'num1', 'num2', demo );
fuc( 100, 200 );


此处输入 alert(num1 * num2);最终会弹出 20000

输入 alert(num1-num2); 最终弹出 -100’

动态函数法一,字符串拼接函数体

function fn1 ( min, max ) {
var sum = 0;
for( var i = min; i <= max; i++ ) {
sum += i;
}
return sum;
}
var fn2 = new Function( 'min', 'max', 'var sum = 0;'
+'for( var i = min; i <= max; i++ ) {'
+'sum += i;'
+'}'
+'return sum;');
console.log( fn1( 2, 5 ) ); // 14
console.log( fn2( 2, 5 ) ); // 14


动态函数法二,利用dom元素保存函数体

<body>
<div id='code' style='display: none;'>
var sum = 0;
for( var i = min; i <= max; i++ ) {
sum += i;
}
console.log( sum );
return sum;
</div>
</body>
<script>
// 写一个函数, 有两个参数. 例如: num1, num2
// 那么求 从 num1 累加到 num2
// fn( 2, 5 )  =>  2 + 3 + 4 + 5 = 14
var fn3 = new Function( 'min', 'max', tool('code') );
function tool( idName ) {
var elem = document.getElementById( idName );
//var code = elem.innerHTML;  // 会把小括号等转义成 <
var code = elem.lastChild.nodeValue;  // 就是方法体
elem.parentNode.removeChild( elem );
elem = null;
return code;
}
fn3( 2, 5 );
</script>


函数相关的一些参数

arguments

凡是函数调用,都会默认含有一个 arguments 对象,可以看作 “数组”.里面存储着调用时传入的所有参数. 可以使用数组的索引访问这些参数

例如: 写一个函数, 在参数中写任意个参数, 最后求其和

function sum() {
// 所有的参数都会存储到 arguments 中
var sum = 0;
for ( var i = 0; i < arguments.length; i++ ) {
sum += arguments[ i ];
}
return sum;
}
console.log( sum( 1,2,3 )); // 6


取最后一个参数 arguments[ length - 1]

- 案例:利用 arguments 实现混入(扩展)

- 要求一: extend( o ) => 可以将 o 混入到当前对象 this 中

- 要求二: extend( o1, o2 ) => 可以将 o2 混入到 o1 中

function extend() {
var args = arguments;
if ( args.length == 1 ) {
// 混入到 this 中
for ( var k in args[ 0 ] ) {
this[ k ] = args[ 0 ][ k ];
}
} else {
// 混入到 arguments[ 0 ]中
for( var k in arguments[ 1 ] ) {
args[ 0 ][ k ] = args[ 1 ][ k ];
}
}
}
var o1 = { name: '张三' };
var o2 = { age: 19, gender: '男' };
var o3 = {};
// extend( o1, o2 );
o3.extend = extend;
o3.extend( o1, o2 );   // o1, o2, o3 分别怎么变化?
// o1 被 o2混入,  o2不变,  o3还是 {}只是多了个 extend方法


函数名.length

函数名.length, 即函数的 length 属性. 表示 定义函数 时, 参数的个数

如果定义函数的时候, 定义了参数. 但是调用的时候又没有传递该参数. 那么该参数在函数内就是 undefined

function A( a, b ) {}
console.log( A.length );    // 2


函数.name 返回的是函数名

function A( a, b ) {}
console.log( A.name );      // A


函数的引用 callee 和 caller

js 中函数也是一个对象

-> callee 在函数的内部, 它表示 当前函数 的引用

-> caller 表示调用函数

callee,一般在函数内部, 实现函数递归的时候, 我们一般使用 callee 表示函数的引用

// 语法:arguments.callee 表示当前引用
function foo() {
console.log( arguments.callee === foo );
}
foo();  // true;
// 一般在函数内部, 实现函数递归的时候, 我们一般使用 callee 表示函数的引用


传统递归

function fn () {
fn(); // 自己调用自己
}
fn();


新方法,使用 callee 来递归

由于js是弱类型语言,以防 fn 被乱赋值,如 fn = 0 后,fn()调用 可能出现各种乱七八糟的问题

function fn() {
arguments.callee();  // 使用 callee 来递归
}
fn();


caller 表示调用函数,就是在被调用函数中, 获得调用函数的引用

// 语法: 函数名.caller
function f2 () {
console.log( f2.caller );
}
f2();   // null

function fn() {
f2();
}
fn();   // function fn() { .. }


eval 函数,eval与 Function的使用比较

(1)eval 函数与 Function 功能类似. eval 可以直接将字符串作为代码来执行.

语法: eval( 语句字符串 )

注意, 它与当前代码处于同一个作用域。

(eval很强大,熟悉的情况下用,可以实现很牛的功能,但是不熟悉就使用,会出现很多很危险的漏洞,所以开发中一般不建议使用,甚至禁止使用)

// 一般书写代码
// var num = 123;
// console.log( num ); // => 123

// 可以直接调用 eval 函数, 来实现字符串代码
eval( 'var num = 123;' );
eval( 'console.log( num );' );
alert( num );   // 123


(2)Function

语法:Function 是用来生成函数中的, 所以如果要执行, 需要调用

var fn = new Function ( ' alert( "执行 Function" );' );

// => 生成了 一个函数 function f () { alert ... }

fn(); // 执行


立即执行函数,又称作 自调用函数

(new Function ( ' alert( "执行 Function" );' ))();

(function () {
alert ( '立即执行函数' );
})();
// 立即执行函数又称作 自调用函数,可以有效的控制作用域


json格式,json字符串转对象

json 格式( 严格的国际通用数据表示协议, 结构 )

json 格式 有两种结构 1: { } 2: [ ]

注意: json 格式中, 键名也必须使用双引号括起来.

说明:(在 js 中使用的 json 对象,是 js 一个对象格式, 相对较松散,所以键名不加双引号也不会报错,但是一旦国际化,就十分不规范,所以要括起来)

将字符串变成对象,有三种做法

eval 做法

var data = '[ { "name": "张三", "age": 19, "gender": "男"}, { "name": "李四", "age": 18, "gender": "女"} ]';
var o1 = eval( "(" + data + ")" );   // 注意一个习惯. 就是数据两端加上圆括号


Function 做法

var data = '[ { "name": "张三", "age": 19, "gender": "男"}, { "name": "李四", "age": 18, "gender": "女"} ]';
var o2 = ( new Function( 'return ' + data ) )();


JSON.parse( ), 使用 ES5 中引入的标准处理 JSON 的语法,要求必须用引号引起来

var data = '[ { "name": "张三", "age": 19, "gender": "男"}, { "name": "李四", "age": 18, "gender": "女"} ]';
var o3 = JSON.parse( data );


为何 eval 转换 json格式的字符串 需要使用圆括号???

因为eval 函数,本质是执行 js 代码, 所以隐含的数据里面 { } 实际上是代码块的含义。

所以 json中的 { }读做了代码块的开始和结束,而加了括号以后,里面内容就变成了表达式,就不会出错了。

// eval 函数,本质是执行 js 代码的
// json 两种格式,[] 没问题,{} 就会有问题,被看成了代码块
var s1 = '{ }'; // 空语句,没有任何结果
var s2 = '{ 12345 }';   // 12345
var o1 = eval( s1 );
var o2 = eval( s2 );
console.log( o1 );  // undefined
console.log( o2 );  // 12345


这里把 name: 当成了一个标记语言,而 真正的语句 就只有 “张三”,所以就得到字符串

var s1 = '{ name: "张三" }'
var o1 = eval( s1 );    // 张三,没有得到对象, 而是得到了一个字符串


标记语言

label123:
while ( true ) {
console.log( '第一层循环开始' );
while ( true ) {
console.log( '第二层循环开始' );

while ( true ) {
console.log( '第三层循环开始' );
break label123;
console.log( '第三层循环结束' );
}

console.log( '第二层循环结束' );
}
console.log( '第一层循环结束' );
}
console.log( 'over' );
// 结果
// 第一层循环开始
// 第二层循环开始
// 第三层循环开始
// over


就算加上 ” “变成标准 JSON 格式,也会报错,相当于执行 “name”:”张三”

var s1 = '{ "name" : "张三" }';
var o1 = eval( s1 );    // 会报错   Uncaught SyntaxError: Unexpected token :(…)


加了括号以后,里面内容就变成了表达式。所eval要加( );

var s1 = '{  }';
var o1 = eval("(" + s1 + ")");  // 空对象

var s2 = '{ name: "张三" }'
var o2 = eval("(" + s2 + ")");  // { name: "张三" }


函数的原型链结构

Function可以用来建函数,所以在 js 中 函数 是 Function 的实例

function Person() {}
var p = new Person();


p是构造函数 Person 的实例,在该角度上看,函数就是对象,Function就是构造函数,那么我们可以得到 构造-实例-原型 三角形

Person, Function对象, Function.prototype

function Person( ) { };

Person函数 由 Function构造函数对象 构造,

Person函数 . __proto__ => Function.prototype

Function 构造函数对象 . prototype => Function.prototype

函数的三角形图1



而Function 是构造函数,它也是函数对象,所以他也是由 Function构造的,所以鸡生蛋还是蛋生鸡的问题来了。。

Function 既是构造函数,也是实例

函数的三角形图2



所有函数对象的原型对象时 Function.prototype 函数的原型链结构-(不考虑对象的原型)



完整的原型链结构-(包含对象与函数)



instanceof 运算符

-> a of b -> b 的 a

-> instanceof ?

-> 错觉:判断某一个对象是否为某一个构造函数所创建出来的

instanceof 语法:

返回值 boolean, 用法: 对象 instanceof 构造函数

// 判断该对象是否 为 构造函数 的 实例 ? 错误

* 判断 构造函数的 原型属性 是否在对象的原型链上 *

function Person() {}
var p1 = new Person();      // 按照原有的原型结构来创建
// p1 -> 原来的 Person.prototype -> Object.prototype -> null
// 设置原型
Person.prototype = {};  // {} 不在 对象原型链 上
console.log( p1 instanceof Person ); // false


function Person () {}
// 设置原型
Person.prototype = {};
var p1 = new Person();  // p1 -> 新的 Person.prototype. 即 {} -> Object.prototype -> null
console.log( p1 instanceof Person );   // true 就在判断 {} 是否在 p1 的原型链上


通过原型链, 可以重新定义 js 的继承

js 的继承: 就是利用对象的动态特性添加成员, 或直接替换对象的方式修改原型链结构. 使得当前对象的原型链上的对象具有某些成员. 那么我的当前对象就可以使用这些成员了.

p -> Person.prototype -> Object.prototype -> null 在中间任意一处添加方法,p也就有了方法

p -> Person.prototype -> {} -> Object.prototype -> null 或者直接修改原型链,添加一个对象{},添加方法

过多的依赖原型链继承, 会损耗 性能

如果必须使用原型链继承, 最好提供一些快速访问的方法

这样非常损耗性能

var o = {
method: function () {
console.log( '我是一个非常顶级的方法' );
}
};
function Person () {}
Person.prototype = o;

function Student () {}
Student.prototype = new Person();

var s = new Student();
s.method();
// 先找自己,自己没有,
// 去原型Student.prototype中找,就是Person构造函数的实例, 没找到
// 再去上面 Person.prototype 中找,就是 o, o中有,终于找到了,返回,可以调用


改良,提供快速访问的方法,避免原型链检索,this.method并不占什么空间,存的是引用,实质函数,还是顶层的函数

var o = {
method: function () {
console.log( '我是一个非常顶级的方法' );
}
};
function Person () {}
Person.prototype = o;

function Student () {
this.method = Person.prototype.method;
}
Student.prototype = new Person();

var s = new Student();
s.method();
// 先找自己,自己就有 this.method = Person.prototype.method,直接找到,直接调用。快速访问,提高了性能


html的原型链结构非常的深,它就是采用这种存引用,快速访问的方法,避免了原型链检索,提高了性能
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息