重学原生js

一:js数据类型:js有两种数据类型,分别是:

重学原生js_第1张图片

js有两种数据类型,分别是:

  • 原始类型:underfine,null,string,number,boolean
  • 引用类型(类):对象。number,string,boolean都有对应的包装类对象。

原始数据类型直接存储在栈(stack)中的简单数据段,占据空间小、大小固定,属于被频繁使用数据,所以放入栈中存储;

引用数据类型存储在堆(heap)中的对象,占据空间大、大小不固定,如果存储在栈中,将会影响程序运行的性能;引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体;

下面是两种类型的对比:

var str1 = 'a'; ----------------------原始类型
var str2 = new String('a'); ------包装类型

但是str1原始类型也能像对象一样使用str1.length(),因为js有一个隐藏的机制,当把一个原始类型试图做引用类型(对象)一样去操作时,会智能的将它转化为包装类型对象。但是访问完后这个临时包装类型对象会被销毁掉,接下来str1.a = 5;alert(str1.a)就会返回underfiner

下面是面试经常考到的判断类型的几种方法:

typeof 和 instanceof

typeof:适用于判断基本类型、方法对象等。因为它判断所有对象,都返回对象
instanceof:适用于判断对象(object)。因为它判断的比较详细

typeof 123;          -----------------number  js的原始类型之一
typeof true;         -----------------boolean js的原始类型之一
typeof funtion;      -----------------function  疑问**********
typeof(undefined);   -----------------undefined undefined是js的原始类型之一
typeof new object(); -----------------object object是js的引用类型
typeof [1,3];        -----------------object 数组是对象,对象是js引用类型
typeof NaN;          -----------------number NaN是一个特殊的number类型
type null;           -----------------object null是js原始数据类型,这是语法bug
如上如果typeof用来判断数组,返回object不够详细

对应对象详细判断,可以用instanceof,它是基于原型链的。这个方法期望左操作数为对象类型,如果为基本类型直接返回false。
[1,2] instanceof Array           返回true
new Object() instanceof Array    返回false,空对象不是array
null instanceof Object           返回false null如上所说是个bug,不是object



在使用 typeof 运算符时采用引用类型存储值会出现一个问题,无论引用的是什么类型的对象,它都返回 "object"。
ECMAScript 引入了另一个 Java 运算符 instanceof 来解决这个问题。instanceof 运算符与 typeof 运算符相似,
用于识别正在处理的对象的类型。与 typeof 方法不同的是,instanceof 方法要求开发者明确地确认对象为某特定类型。
例如:var oStringObject = new String("hello world");
alert(oStringObject instanceof String);	//输出 "true"

两种类型的区别:

  • 从一个变量向另一个变量复制基本类型的值,会创建这个值的一个副本;
  • 从一个变量向另一个变量复制引用类型的值,复制的其实是指针,因此两个变量最终都指向同一个对象;
var supera = {name:"sunq",sex:"man"};
var sub = supera;
sub.sex = "woman";
console.log(supera.sex);//输出woman

二:表达式 和 运算符

具体可参考[JavaScript]运算符-慕课网

重学原生js_第2张图片

重学原生js_第3张图片

三:对象和this

js的对象和继承有很多种实现方式如下:

重学原生js_第4张图片

var Book = function(id,bookname,price){
    this.id = id;
    this.bookname= bookname;
    this.price = price;
}
Book.prototype.display = function(){
    ...
}
var book = new Book(10,'js',50);

这段代码中类的实现和继承关系如下图:
js类和继承这块内容太多,刚入门同学可以放后面学习。
推荐大家也看看犀牛书,这块需要下功夫慢慢学的

重学原生js_第5张图片

对象中包含一系列的属性,这些属性是无序的。每个属性都有一个字符串的key和对应的value。对象的每一个属性都有一些标签,如图右边。每一个对象都自带(预设)三个属性proto、class、extensible。

重学原生js_第6张图片

如上图是对象的结构图。因为obj的原型foo的prototype.z=3,所以obj.z第一步回去找foo有没有z属性,如果没有就找原型foo的prototype里有没有z属性。

创建对象的方法:

1:定义并创建对象的实例var person=new Object(); person.name='a'

2:literals直接创建对象实例 如 对象字面量 var a = {name:'xiaoming' , age:'24' }

3:使用函数来定义对象,然后创建新的对象实例 如 new构造器,var obj = new Foo()如下图

重学原生js_第7张图片

用new去构造一个对象的主要特点是,这个实例的原型会指向构造函数的prototype属性

重学原生js_第8张图片

重学原生js_第9张图片

用构造器创建对象如上,Foo()会有一个prototype属性,obj3的原型(proto)会指向构造器Foo的prototype属性。obj3为Foo的实例。当我们用function Foo(){}这个函数声明去创建一个空函数对象时,Foo会自带constructor、prototype、__proto__属性,其中__proto__为chrome浏览器创建的,ES5没有。Foo的__proto__指向Object.prototype,而ToString()、ValueOf()都是Object的prototype上的。所以原型链就产生了,Object类是Foo的父类,obj3也能用ToString()

获取对象的属性值:

obj3.y 或者 obj

This:同一个函数在不同的调用方式下,this是不同的。

1.全局作用域下的this:指向全局对象即window,this.a=1,其实就是全局变量a为1

重学原生js_第10张图片

2.一般函数下的this:还是指向全局变量window,不过严格模式下指向undefined

重学原生js_第11张图片

3.作为对象方法的函数的this:指向调用它的对象,不光第一种第二种也是,如下图

重学原生js_第12张图片

4.对象原型链上的this: var p = Object.create(o)方法创建一个原型为o的空对象p,或者说空对象p的原型指向o,如下图所示。就是说原型链上的this也指向调用它的对象

重学原生js_第13张图片

5.构造器中的this:当用构造器来创建对象时,构造器里的this指向一个空的对象,并且这个对象的原型指向MyClass()的prototype。如果构造器的内部没有return语句时就会默认return this出去。

重学原生js_第14张图片

6.
function fn1() {
console.log(this); 
}
var oBtn = document.getElementById('btn1');
oBtn.onclick = fn1;
//输出  

四:作用域 和 闭包

js中有两种作用域:

  • 块级作用域:两个花括号中间的任何变量,外部都是不可见的,我们称之为块级作用域
  • 函数作用域:函数中定义的任何变量,外部都是不可见的。

4.1 作用域

c语言是块级作用域:j在if的{}里面,所以外面不能访问 ;js是函数作用域:因为var i是在test方法里的,所以test能访问到

#include  
void main() { 
   int i=2; 
   i--; 
   if(i) { 
       int j=3; 
   } 
   printf("%d/n",j); 
}

functin test(){ 
   for(var i=0;i<3;i++){}
   alert(i); 
} 
test();  弹出3

4.2 闭包

闭包是指有权访问另一个函数作用域中的变量的函数。创建闭包的常见方式,就是在一个函数内部创建另一个函数。如下代码createComparisonFunction是一个作用域,内部匿名函数也是一个作用域。createComparionFunction只能访问自己这个外面的作用域,而不能访问内部匿名函数的作用域;内部匿名函数可以访问两个作用域,自己以及createComparisonFunction。

function createComparisonFunction(propertyName) {
    return function (object1,object2) {
        var value1 = object1[propertyName];
        var value1 = object2[propertyName];

        if(value1value2){
          return 1;
        }else {
          return 0;
        }
    };
}
var compareNames = createComparisonFunction("name");
var result = compareNames({name:"Nicholas"},{name:"Greg"});

4.3 经典面试题

闭包是经典面试题,常以选择题、编程题的形式出现。大家请注意,尤其选择题形式频率高的离谱!

推荐一篇文章写的挺好用9种办法解决 JS 闭包经典面试题之 for 循环取 i

五:函数

函数是一块js代码,被定义一次,可以多次执行、调用。js中的函数比较特殊,也是对象。所以函数也能像一般对象那样操作和传递,比如给函数创建属性a、b、c,或者调用函数对象上的一些方法。函数的返回值由代码方法体中的return决定。如果没有return语句,则默认return undefinede。函数有多种创建方法和调用方式,不同的使用方法下会有一些细微的差别。

创建方式:函数声明、函数表达式、函数构造器(不常用)

重学原生js_第15张图片

重学原生js_第16张图片

函数申明和函数表达式的区别就是,函数申明会被前置。如上图第一个可以调用方法返回3,第二个返回undefined。因为函数声明的function add()会被提前到第一句,而函数表达式的var add会被提前到第一句。也是函数前置和变量前置的区别。函数表达式其实就是用的匿名函数赋值给变量,把函数当成值来使用的情况下,都可以使用匿名函数。不过这并不是匿名函数的唯一用途

重学原生js_第17张图片

调用方式:

  • 方法调用模式
  • 函数调用模式
  • 构造器调用模式
  • apply调用模式

六:隐式转换

js中运算符很多,常用+ - == ===,下面总结这几个运算符的隐式转换。

+:当加号运算符左右两边类型相同时,比如两个int则运行数字加法;如果一边是int,一边是string,int将隐式转换为string,进行字符串拼接;

-:当减号左右两边都是int,则进行数字减法;如果一边是int,一边不是int,将尝试将不是int类型的转化为int类型,再进行数字减法。

===:严格相等,首先判断两边的类型是否一样,如果类型都不一样直接返回false。然后再判断值是否一模一样。需要注意的是NaN===NaN返回false,因为NaN无论跟什么都不是完全相等的;如果两边是对象,[1,2]===[1,2]返回false,因为除非是同一个累new出的两个相同的对象才行。

==:一般相等。这个就会有隐式转换了。如果==两边类型不一样,会尝试将两边类型转化为一样再对比值是否相同。比如true==1,会将boolean转化为0或1,再对比;123=='123',js将尝试将string转化为int再对比;null==underdefine返回true;一边是对象,一边是基本类型,将尝试将对象转换为基本类型。这部分具体实例太多,总结在下一章了懵懂老头:重学原生js 下篇

七:声明提升

语法:

  • ECMAScript 的解释程序遇到未声明过的标识符时,用该变量名创建一个全局变量,并将其初始化为指定的值。(可以不声明,这样自动变成全局变量)
  • 如果都只是声明的话,函数声明提升的优先级要比变量提升高

1.javascript的变量声明和函数声明提升

如果都只是声明的话,函数声明提升的优先级要比变量提升高:

var a; // 无论变量声明在函数声明前面还是后面,都会被函数覆盖

function a(x){}
alert(a);   

输出 function a(x){}

但如果变量有赋值的话,就像题主这样,函数是无法覆盖的:

var a = 1;

function a(x){}
alert(a);   

输出 1

变量提升需要注意,面试基本必考。如下

var foo=function(x,y){
    return x-y;
}
function foo(x,y){
    return x+y;
}
var num=foo(1,2);

上面这段代码实际上的执行顺序是

var foo,num;
function foo(x,y){
    return x+y;
}
foo = function(x,y){
    return x-y;
}
num = foo(1,2);   //-1

js的运行机制是先申明变量,即使var是在后面的代码里才写的,执行时也会提前到第一行。然后函数的执行优先级更高,变量赋值次之。

如上代码因为foo变量被赋值两次,函数式声明优先级高先执行,变量式声明优先级低后执行。后面执行的覆盖了前面执行的。

对javascript的预编译中提前声明不是很了解 - SegmentFault

八:变量回收

1.局部 JavaScript 变量

在 JavaScript 函数内部声明的变量(使用 var)是局部变量,所以只能在函数内部访问它。(该变量的作用域是局部的)。

您可以在不同的函数中使用名称相同的局部变量,因为只有声明过该变量的函数才能识别出该变量。

只要函数运行完毕,本地变量就会被删除。

2.全局 JavaScript 变量

在函数外声明的变量是全局变量,网页上的所有脚本和函数都能访问它。

3.JavaScript 变量的生存期

JavaScript 变量的生命期从它们被声明的时间开始。

局部变量会在函数运行以后被删除。

全局变量会在页面关闭后被删除。

4.向未声明的 JavaScript 变量来分配值

如果您把值赋给尚未声明的变量,该变量将被自动作为全局变量声明。

这条语句:

carname="Volvo";

将声明一个全局变量 carname,即使它在函数内执行。

九:原始类型和引用类型

  • 原始类型(直接放在栈里):undefined,null,boolean,number,string
  • 应用类型(放在堆里,指针放在栈里):object,boolean,number,string

分开放是因为栈的空间有限,放一些长度确定可以。长度不固定的比如对象,就把它的指针放在栈里,因为指针长度固定。

十:特殊值

NaN: 属性是代表非数字值的特殊值。该属性用于指示某个值不是数字。可以把 Number 对象设置为该值,来指示其不是数字值。 方法 parseInt() 和 parseFloat() 在不能解析指定的字符串时,或者计算0/0时,就返回这个值。 提示:请使用 isNaN() 全局函数来判断一个值是否是 NaN 值。if(NaN==NaN)得到false,因为NaN代表是非数字值,这个值不一定是哪个非数字值。

this:Javascript语言的一个关键字。它代表函数运行时,自动生成的一个内部对象,只能在函数内部使用。随着函数使用场合的不同,this的值会发生变化。但是有一个总的原则,那就是this指的是,调用函数的那个对象。

十一:return

return大致分为两种应用场景

1、在函数体内

当执行到return关键字后,结束函数,不再执行return语句后面的语句;

2、在循环体内

  • 如果循环在最外层作用域,那么情况同上1直接结束函数;比如for循环内return,直接结束循环。如果for循环在函数体内,那么不光结束循环,而且结束函数;
  • 注意,如果每个循环体是函数,当执行到return关键字后,不再执行当前循环体内return语句之后的语句。会结束当次循环体,执行下一个循环;

具体如下代码,两种循环,return时效果不同,得擦亮眼睛

function containsRepeatingLetter(str) {
    var arr = str.split(''),result=false;
    // 如果是for循环,直接结束循环,并结束函数
    for(var i=0;i

十二:++i和i++

++前置和后置的区别在于先自加再赋值,还是先赋值再自加。

我的记忆方式时,遇到事情大家都先从自己考虑为出发点。

++i就是先自加再赋值,i++就是先赋值再自加

 function out(x){
     var temp = 2;
     function inside(y){
         document.write( x + y + (temp--));
     }
     inside(5);
 }
 out(3);      //输出:10

十二:原生js模仿Ajax

1.content-type与参数间的关系

注意设置的content-type,跟传参时的格式是相对应的。最方便的还是第一种

content-type 传参要求
application/json json字符串格式,可以用JSON.stringfy()处理
application/x-www-form-urlencoded 按照 key1=val1&key2=val2 的方式进行拼接
form-data 需要用到FormData对象

如下代码简单封装xhr模仿Ajax

var myAjax = function (para) {
  var xhr = new XMLHttpRequest();

  xhr.open("POST", para.url, true);

  // 添加http头,发送信息至服务器时内容编码类型
  xhr.setRequestHeader("Content-Type", "application/json");

  xhr.send(para.data ? para.data : '');

  xhr.onreadystatechange = function() {
    if (xhr.readyState == 4 && (xhr.status == 200 || xhr.status == 304)) {
      para.success(xhr.responseText);
    }
  };
};

调用myAjax

var para = JSON.stringify({
    userName: userName,
    score: gameData.score - 10,
    gameTime: gameData.time
});

myAjax({
    url: 'http://39.104.22.73:8081/ScoreCreate/foreend',
    data: para,
    success: function (data) {
        console.log('upload success backback');
        console.log(data);
    }
});

2.url/body请求方式的区别

Post 方法参数写在body中和写在url中有什么区别?

十三:Call/apply

call的官方解释有很多种版本,

  • 犀牛书中:调用一个对象的一个方法,以另一个对象替换当前对象。
  • W3School中:使用call()方法,您可以编写能够在不同对象上使用的方法。
  • W3School中还说:call() 方法是预定义的 JavaScript 方法,它可以用来调用所有者对象作为参数的方法,通过 call(),您能够使用属于另一个对象的方法。

个人觉得上面的解释都太绕,不如直接说作用域

以另外一个对象替换当前对象,可以理解为用另外一个作用域替换当前作用域

使用另外一个对象的方法,就是继承了另外一个对象的作用域,可以直接使用该作用域的方法

-----------------------例1---------------------------------
function a() {
    this.x = 'a方法的x'
}
function b() {
    console.log(this.x);
}
b.call(new a());    //执行结果是打印‘a方法的x’;

//个人理解是:b可以使用a的作用域,等于扩宽了b的作用域,所以执行b时可以访问到变量x

-----------------------例2---------------------------------
function f() {
    this.a = 'a';
    this.b = function () {
        console.log('b');
    }
}
function e() {
    f.call(this);       //e继承了f的属性和方法
}
var c = new e();
console.log(c.a);  //  'a'
c.b();   //   'b'

如下是我对call的认知,有多种解释方法:

A.call(d,"MaYun","MAN"); // A是一个函数

  • 在对象d的作用域中调用function A,
  • 要执行的还是A但是作用域由传入的第一个参数决定。
  • 在d的作用域下执行A方法
  • 用d替换A,使用d的作用域
  • 因为call方法,您能够使用d的作用域

call和Apply区别是,call传参要每个罗列,apply传参要用数组,下面是实例对比:

function A(name,sex) {
    this.name = name;
    this.sex = sex;
}

//构造函数式继承,B继承A
function B(name,sex,goddess) {
    A.apply(this,[name,sex]);   //借助apply方法可以实现继承
    this.goddess = goddess;
}
var b = new B("sunquan","man","zyy");
console.log(b.name+" "+b.sex+" "+b.goddess);

--------------------------------------------------------------------------------------------

//构造函数式继承,B继承A
function C(goddess,name,sex) {
    this.goddess = goddess;
    A.call(this,name,sex);//借助call方法也可以实现继承
}
var c = new C("Mic","Jane","woman");
console.log(c.name+" "+c.sex+" "+c.goddess);

-------------------------------------------------------------------------------------------

function D() {}
var d = new D();
A.call(d,"MaYun","MAN");   //在d的作用域下,执行A方法
console.log(d.name+" "+d.sex);

应用1:如何理解Array.prototype.slice.call(数组)

var a = [1,2,3];
var b = Array.prototype.slice.call(a);  //b的值是 [1,2,3]
Array是个抽象的对象,他是所有数组的类。slice大家都知道得用某个数组比如a,来调用。
上面这句代码Array不是具体的一个数组,比如a。
显然是不能调用slice的,所以使用call(a),a来代替Array,相当于 a.slice();

var a = Array.prototype.slice.call(arguments);
a.shift();
方法体内的arguments是个类数组,不能直接当作数组用。
通过上面代码可以在arugments的作用域下使用slice方法,
把arguments就可以使用Array的作用域,这时arguments就相当于是个Array了。

应用2:分辨数据类型

var a = {}, b = [] , c = 1, d = 'sunq';

Object.prototype.toString.call(a);//[object,Object]
Object.prototype.toString.call(b);//[object,Array]
Object.prototype.toString.call(c);//[object,Number]
Object.prototype.toString.call(d);//[object,String]

注意:使用obj.toString()是不能得到类型的。
原因:Array,Function等类型作为Object的实例,都重写的了toString方法。
因此在调用时,是调用了重写后的方法,而不是原型链上的toString()方法

十四:监听关闭页面事件(onbeforeunload)

当窗口即将被卸载(关闭)时,会触发该事件.此时页面文档依然可见,且该事件的默认动作可以被取消.

当该事件返回的字符串(事前设置好的event.returnValue的值)不为null或者undefined时,弹出确认窗口让用户自行选择是否关闭当前页面。

// 如下代码会使,用户关闭网页时弹出确认框。老版本浏览器弹框文本是returnValue赋的值,新版不再展示
window.onbeforeunload = function (e) {
  e = e || window.event;

  // 兼容IE8和Firefox 4之前的版本
  if (e) {
    e.returnValue = '关闭提示'; // 如果不设置此属性,则不会弹框
  }

  // Chrome, Safari, Firefox 4+, Opera 12+ , IE 9+
  return '关闭提示';
};

你可能感兴趣的:(java,javascript,开发语言)