10bet理解Javascript的动态语言特性_javascript技巧_脚

来源:http://www.chinese-glasses.com 作者:Web前端 人气:143 发布时间:2020-04-22
摘要:时间: 2019-09-23阅读: 70标签: js知识 Javascript是一种解释性语言,而并非编译性,它不能编译成二进制文件。 首先用一个例子指出来constructor存在形式。 理解动态执行与闭包的概念 functi

时间: 2019-09-23阅读: 70标签: js知识

Javascript是一种解释性语言,而并非编译性,它不能编译成二进制文件。

首先用一个例子指出来constructor存在形式。

理解动态执行与闭包的概念

function Fruit(){ }var f=new Fruit();console.log(f.constructor);//打印出Fruit()

动态执行:javascript提供eval()函数,用于动态解释一段文本,并在当前上下文环境中执行。

由上面的代码我们总结出结论1:上面的代码在控制台可以看出constructor是指向构造器Fruit的引用。

首先我们需要理解的是eval()方法它有全局闭包和当前函数的闭包,比如如下代码,大家认为会输出什么呢?

function Fruit(){ this.name="水果"}//var f=new Fruit();function Apple(){this.name="苹果";}Apple.prototype=new Fruit();var apple=new Apple();console.log(apple.constructor);//依然打印出Fruit()Apple.prototype={};//空对象的构造器是Object()apple=new Apple();console.log(apple.constructor);//指向Object()
var i = 100;function myFunc() { var i = 'test'; eval;}myFunc; // 100

这个地方就有点奇怪了。这个constructor到底指向的是那个实例的构造器?

首先我们来看看先定义一个变量i=100,然后调用myFunc这个函数,然后修改局部变量i,使他从值 'test'变成'hello', 但是我们知道eval的含义是立即执行一段文本的含义;因此上面的代码我们可以写成如下代码:

根据上面的代码总结出结论2:constructor指向的是原型对象的构造器的引用

var i = 100;function myFunc() { var i = 'test'; { return ;}myFunc; // 100

apple.constructor==Apple.prototype.constructor

这样就很明显了,执行myFunc()这个方法后,i的值从test变为hello的值,但是由于是闭包,i的值为hello,它不能被外部使用,所以浏览器打印的都是100值;

var apple2=new apple.constructor();console.log(apple2.name);

我们都知道eval()是javascript的全局对象Global提供的方法,而如果要访问Global对象的方法,可以通过宿主对象-在浏览器中是window来提供;按道理来说,下面的代码应该也是输出100;如下:

或者

var i = 100;function myFunc() { var i = 'test'; window.eval;}myFunc;
var apple2=new Apple.prototype.constructor();console.log(apple2.name);

然后不幸的是:在IE下不管是window.eval方法输出的都是100;但是在标准浏览器下使用window.eval方法的输出的是100; 因为IE下使用的是JScript引擎的,而标准浏览器下是SpiderMonkey Javascript引擎的,正是因为不同的javascript引擎对eval()所使用的闭包环境的理解并不相同。

打印的都是水果。

理解eval使用全局闭包的场合

我们现在想让他执行的是苹果的打印;这个时候就需要对constructor的指向进行重新指定。

var i = 100;function myFunc() { var i = 'test'; window.eval;}myFunc;

根据上面的两个结论:无论是修改实例的constructor还是构造器的原型constructor属性都是可以达到目的的

在标准浏览器下,打印的是hello,但是在IE下打印的是100;如果使用如下代码:

可以在重写了prototype属性的代码后写下这样的代码

var i = 100;function myFunc() { var i = 'test'; //window.eval; eval.call;}myFunc;
Apple.prototype.constructor=Apple;

也是一样的,也是给eval方法提供一种访问全局闭包的能力;但是在IE下Jscript的eval()没有这种能力,IE下一只打印的是100;不过在IE下可以使用另一种方法得到一个完美的结果,window.execScript()方法中执行的代码总是会在全局闭包中执行,如下代码:

或者在实例化后对实例的constructor进行重新制定。

var i = 100;function myFunc() { var i = 'test'; window.execScript; //eval.call;}myFunc; // 打印hello
 apple.constructor=Apple;

JScript来将eval在全局闭包与函数闭包的不同表现分离出来,而Mozilla的javascript引擎则使用eval()函数的不同调用形式来区分它们。二者实现方法有不同,但是可以使用不同的方式实现全局闭包;

虽然上面的两种方式都能达到目的,但是推荐使用第一种。

理解eval()使用当前函数的闭包

第二种方式需要有一个实例化的对象,而我们只用在对继承的创建完成后才会去实例化,

一般情况下,eval()总是使用当前函数的闭包,如下代码:

等有了实例化对象在去修改constructor,这个时候已经迟了,意义已经不大了,继承的关系已经确定了。

var i = 100;function myFunc() {var i = 'test';eval;}myFunc; // 100

如上代码:因为eval作用与是函数内的代码,所以输出的是全局变量i等于100;

eval()总是被执行的代码文本视为一个代码块,代码块中包含的是语句,复合语句或语句组。

我们可以使用如下代码取得字符串,数字和布尔值;

eval; // trueeval; // stringeval; // 数字3

但是我们不能使用同样的方法取得一个对象;如下代码:

eval('{name:"MyName",value:1}');

如上代码会报错;如下:Uncaught SyntaxError: Unexpected

其实如上那样写代码,{name:"MyName",value:1},eval会将一对大括号视为一个复合语句来标识,如下分析:

第一个冒号成了 “标签声明”标示符。{“标签声明”的左操作数}name成了标签。MyName成了字符串直接量;Value成了变量标示符。对第二个冒号不能合理地作语法分析,出现语法分析期异常;如果我们只有这样一个就不会报错了,如下代码:

输出"MyName";

那如果我们想要解决上面的问题要如何解决呢?我们可以加一个小括号包围起来,使其成为一个表达式语句,如下代码:

eval('({name:"MyName",value:1})')

输出一个对象Object {name: "MyName", value: 1}

但是如下的匿名函数加小括号括起来在IE下的就不行了,如下代码:

var func = eval;

alert; // IE下是undefined

在标准浏览器chrome和firefox是打印function,但是在IE下的JScript引擎下打印的是undefined,在这种情况下,我们可以通过具名函数来实现;如下:

alert; // 打印是function

我们使用eval时候,最常见的是ajax请求服务器端返回一个字符串的格式的数据,我们需要把字符串的格式的数据转换为json格式;如下代码:

// 比如服务器返回的数据是如下字符串,想转换成json对象如下:

var data = '{"name":"Mike","sex":"女","age":"29"}';

console.log;

打印Object {name: "Mike", sex: "女", age: "29"} 就变成了一个对象;

// 或者直接如下 ,都可以

console.log(eval("("+'{"name":"Mike","sex":"女","age":"29"}'+")"));

我们还需要明白的是使用eval或者with语句,他们都会改变作用域的问题,比如使用eval如下代码:

var i = 100;function myFunc { console.log; // 100 eval; console.log; // 10} myFunc;

如上代码,第一次执行的是100,第二次调用eval()方法,使作用域变成函数内部了,因此i变成10了;

理解动态方法调用

Javascript有三种执行体,一种是eval()函数入口参数中指定的字符串,该字符串总是被作为当前函数上下文中的语句来执行,第二种是new Function(){}中传入的字符串,该字符串总是被作为一个全局的,匿名函数闭包中的语句行被执行;第三种情况执行体就是一个函数,可以通过函数调用运算符”()”来执行;除了以上三种之外,我们现在还可以使用call方法作为动态方法来执行;如下代码:

function foo{ alert;}foo.call; // 调用打印 hi: longenfoo.apply; // 调用打印 hi:tugenhua

call方法 使用效果是一样的,都是调用函数,只是第二个参数不一样,apply第二个参数是一个数组或者arguments;

在call和apply中理解this的引用

如果我们将一个普通的函数将作为一个对象的方法调用的话,比如我现在有一个普通的函数如下代码:

function foo;}

现在我们下面定义2个对象如下:

var obj1 = {name:'obj1'};var obj2 = new Object();obj2.name = 'obj2';

那么现在我们使用这2个对象来分别调用哪个上面的普通函数foo;如下:

可以看到,第一次打印的是obj1,第二次打印的是obj2;也就是说第一次的this指针指向与obj这个对象,第二次this指针指向与obj2这个对象;

function foo;}var obj1 = {name:'obj1'};var obj2 = new Object();obj2.name = 'obj2';foo.call; // obj1foo.call; //obj2

我们在方法调用中能查询this引用以得到当前的实例,因此我们也能够使用的下面的代码来传送this的引用;

function foo;}function MyObject(){ this.name = 'myObject';}MyObject.prototype.doAction = function;}// 测试var obj3 = new MyObject;

如上代码先实例化MyObject这个对象,得到实例obj3, 然后调用实例的doAction这个方法,那么当前的this指针就指向了obj3这个实例,同时obj3.name = ‘MyObject'; 所以在调用foo.call时,this指针指向与obj3这个实例,因此alert;就弹出myObject;

使用同样的方法,我们可以传递参数,代码如下:

function calc_area;}function Area() { this.name = 'MyObject';}Area.prototype.doCalc = function{ calc_area.call;};var area = new Area;

如上使用了call方法,并且给call方法传递了2个参数,但是上面的我们也可以使用apply方法和call()方法的不同点就是第二个参数,如上call方法的参数是一个一个的传递,但是apply的第二参数是一个数组或者是arguments,但是他们实现的功能是相同的;

function calc_area;}function Area() { this.name = 'MyObject';}Area.prototype.doCalc = function{ //calc_area.call; calc_area.apply};var area = new Area;

理解javascript对象

Object.defineProperty方法, 该方法是ECMAScript5提供的方法,该方法接收3个参数,属性所在的对象,需要修改对象中属性名字,和一个描述符对象;描述符对象的属性必须是 configurable、enumerable、writable 和value。设置其中的一或多个值,可以修改对应的特性值。

ECMAScript中有2种属性,数据属性和访问器属性

1. 数据属性;

数据属性包含一个数据值的位置。在这个位置可以读取和写入值。数据属性有4个描述其行为的特性;

configurable:表示能否通过delete删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性。这个特性值默认为true。

enumerable:表示能否通过 for-in 循环返回属性。这个特性值默认为true。

writable:表示能否修改属性的值。这个特性值默认为true。

value: 包含这个属性的数据值。读取属性值的时候,从这个位置读;写入属性值的时候,把新值保存在这个位置上,这个特性值默认为undefined;

目前标准的浏览器支持这个方法,IE8-不支持此方法;

比如我们先定义一个对象person,如下:

var person = { name: 'longen'};

我们可以先alert; 打印弹出肯定是longen字符串;

理解writable属性;

10bet,现在我们使用Object.defineProperty()方法,对person这个对象的name属性值进行修改,代码如下:

alert; // longenObject.defineProperty(person, "name", { writable: false, value: "tugenhua"});alert; // tugenhuaperson.name = "Greg"; alert; // tugenhua

如上代码,我们writable设置为false的时候,当我们进行修改name属性的时候,是修改不了的,但是如果我把writable设置为true或者直接删掉这行代码的时候,是可以修改person中name的值的。如下代码:

Object.defineProperty(person, "name", { writable: false, value: "tugenhua"});alert; // tugenhuaperson.name = "Greg"; alert; // Greg

理解configurable属性

继续如上JS代码如下:

var person = { name: 'longen'};alert; // longenObject.defineProperty(person, "name", { configurable: false, value: "tugenhua"});alert; // tugenhuadelete person.name;alert; // tugenhua

当把configurable设置为false的时候,表示是不能通过delete删除name这个属性值的,所以上面的最后一个弹窗还会打印出tugenhua这个字符串的;

但是如果我把configurable设置为true或者直接不写这个属性的话,那么最后一个person.name弹窗会是undefined,如下代码:

var person = { name: 'longen'};alert; // longenObject.defineProperty(person, "name", { value: "tugenhua"});alert; // tugenhuadelete person.name;alert; // undefined

理解enumerable属性

Enumerable属性表示能否通过for-in循环中返回数据,默认为true是可以的,如下代码:

var person = { name: 'longen'};Object.defineProperty(person, "name", { enumerable: true, value: "tugenhua"});alert; // tugenhuafor { alert; // 可以弹出框}

如上是把enumerable属性设置为true,但是如果把它设置为fasle的时候,for-in循环内的数据就不会返回数据了,如下代码:

var person = { name: 'longen'};Object.defineProperty(person, "name", { enumerable: false, value: "tugenhua"});alert; // tugenhuafor { alert; // 不会弹出}

2. 访问器属性

访问器属性有getter和setter函数,在读取访问器属性时,会调用getter函数,这个函数负责返回有效的值,在写入访问器属性时,会调用setter函数并传入新值,这个函数负责如何处理数据,访问器属性也有以下4个特性:configurable:表示能否通过delete删除属性从而重新定义属性,能否修改属性的特性,或者能否把属性修改为访问器属性。这个特性值默认为true。enumerable:表示能否通过 for-in 循环返回属性。这个特性值默认为true。

get:在读取属性时调用的函数,默认值为undefined。set:在写入属性时调用的函数,默认值为undefined。

var book = { _year: 2004, edit: 1};Object.defineProperty(book,"year",{ get: function(){ return this._year; }, set: function { if { this._year = newValue; this.edit += newValue - 2004; } }});book.year = 2005;alert; //2

首先我们先定义一个book对象,有2个属性_year和edit,并初始化值,给book对象再新增值year为2005,而访问器属性year则包含一个getter函数和一个setter函数,因此先会调用set函数,把2005传给newValue,之后this._year就等于2005,this.edit就等于2了;

目前支持Object.defineProperty方法的浏览器有IE9+,Firefox4+,safari5+,chrome和Opera12+;

理解定义多个属性

ECMAScript5定义了一个Object.defineProperties()方法,这个方法可以一次性定义多个属性,该方法接收2个参数,第一个参数是添加或者修改该属性的对象,第二个参数是一个对象,该对象的属性与第一个参数的对象需要添加或者删除的属性一一对应;

var book = { _year: 2004, edit: 1};Object.defineProperties(book,{ _year: { value: 2015 }, edit: { value: 2 }, year: { get: function(){ return this._year; }, set: function{ if(newValue > this._year) { this._year = newValue; this.edit += newValue - this._year; } } }});

如上代码;给book对象设置了3个属性,其中前面两个会覆盖原有的book的对象的属性,三个属性是添加的;

上面确实是给对象设置了多个属性了,那么现在我们如何读取属性了?

ECMAScript5给我们提供了方法 Object.getOwnPropertyDescriptor()方法,可以取得给定属性的描述符。该方法接收2个参数,属性所在的对象和需要读取描述符的属性名称。返回值也是一个对象,如果是访问器属性,这个对象的属性有configurable、enumerable、get 和set;如果是数据属性,这个对象的属性有configurable、enumerable、writable 和value。

我们先来看下数据属性,如下代码获取:

复制代码 代码如下:var descriptor = Object.getOwnPropertyDescriptor;console.log;

Object {value: 2015, writable: true, enumerable: true, configurable: true}

是一个对象,现在的value变成2015了;

但是如果我们来看下访问器属性的话,如下代码:

var descriptor = Object.getOwnPropertyDescriptor;

console.log;

Object.getOwnPropertyDescriptor方法,支持这个方法的浏览器有:

IE9+,firefox4+,safari5+,opera12+和chrome;

function Dog { this.name = name; this.age = age; this.say = function; }}

如上就是一个构造函数,它与普通的函数有如下区别:

1.函数名第一个首字母需要大写,为了区分是构造函数。2.初始化函数的时候需要new下;任何函数,只要它是通过new初始化的,那么他们就可以把它当做构造函数;比如如下初始化实例化2个对象:

var dog1 = new Dog;var dog2 = new Dog;

那么我们现在可以打印console.log;打印出来肯定是wangwang1,console.log;打印出来是wangwang2;

dog1和dog2分别保存着Dog的一个不同的实例,且这两个对象都有一个constructor属性。该属性指向Dog,如下代码:

alert(dog1.constructor === Dog); // truealert(dog2.constructor === Dog); // true

同时实例化出来的对象都是Object的实例,可以通过instanceof 来检测如下代码:

alert(dog1 instanceof Object); // truealert(dog2 instanceof Object); // truealert; // truealert; // true

构造函数的缺点:就是每个方法都需要在每个实例上重新创建一遍,比如上面的实例化2个Dog对象,分别为dog1和dog2,dog1和dog2都有一个say方法,但是那个方法不是同一个Function的实例,如下代码:

alert(dog1.say === dog2.say); // false

因此我们需要引入原型模式;原型模式就是要解决一个共享的问题,我们创建的每个函数都有一个prototype属性,这个属性是一个指针,指向一个对象,而这个对象的用途就是让所有的实例共享属性和方法;比如还是上面的代码改造成如下:

function Dog() {};Dog.prototype = { name: 'wangwang', age:'11', say: function; //wangwang }}var dog1 = new Dog; var dog2 = new Dog;alert(dog1.say === dog2.say); // true

如上打印 dog1.say === dog2.say,他们共享同一个方法;为什么会是这样的?

本文由10bet发布于Web前端,转载请注明出处:10bet理解Javascript的动态语言特性_javascript技巧_脚

关键词:

最火资讯