博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
javascript常见知识点
阅读量:4087 次
发布时间:2019-05-25

本文共 14257 字,大约阅读时间需要 47 分钟。

1.如题

function Foo() {    getName = function () { alert (1); };    return this;}Foo.getName = function () { alert (2);};Foo.prototype.getName = function () { alert (3);};var getName = function () { alert (4);};function getName() { alert (5);} //请写出以下输出结果:Foo.getName();getName();Foo().getName();getName();new Foo.getName();new Foo().getName();new new Foo().getName();

这道题的主要综合考察了的JavaScript的综合能力,包含了变量定义提升、this指针指向、运算符优先级、原型、继承、全局变量污染、对象属性及原型属性优先级等知识.

第一问

Foo.getName();

先简要的看一下上半部分的代码: 首先创建了一个名为Foo的函数;

接下来给Foo的静态属性getName存储了一个匿名函数;
给Foo的原型对象创建一个名为getName的匿名函数;
之后又通过函数变量表达式创建了一个getName的函数;
最后再声明一个叫getName函数。

那么,第一问就毫不犹豫的输出2;

第二问

getName();

第二问,直接调用getName函数。则调用当前作用域内的getName函数,所以这里应该直接把关注点放在4和5上.

4与5的区别在函数声明和函数表达式两种方式的区别:

  • 函数声明: 在JS中函数声明会被提升到作用域的最前面,即使代码写在最后面,也会被提升值最前面;
  • 函数表达式: 存储函数的变量会被提升至作用域的最前面,但是函数的表达式的赋值过程在运行的时候才会执行,并且只能等到赋值操作完成时才会被调用; 下面分别看一下两者运行起来的差别:

函数声明

var getName;    console.log(getName)//function getName() {alert(5);}    getName()//5    function getName() {        alert (5);    }

函数表达式

var getName;    console.log(getName)//undefined    getName()//Uncaught TypeError: getName is not a function    var getName = function() {        alert (4);    }

那么, 答案也就很明确了, 为4.

第三问

Foo().getName();

首先,执行了Foo函数,并返回了this,this的指向是由所在函数的调用方式决定的。而此处的直接调用方式,this指向window对象.遂Foo函数返回的是window对象,相当于执行window.getName();即执行getName()由于在函数Foo执行的过程中,其函数内部的getName的赋值操作会将var getName = function () { alert (4);}给赋值为function () { alert (1);}, 此时全局 作用域中的getName就变为function () { alert (1);}. 从而该答案为1.

第四问

getName();

该问题和三个相同,由于getName被修改过了.所以答案为1.

第五问

new Foo.getName();

该问题要考虑函数执行的优先级顺序:

  • 19 圆括号 n/a ( … )
  • 18 成员访问 从左到右 … . …
  • 18 new (带参数列表) n/a new … ( … )
  • 17 new (无参数列表) 从右到左 new …
  • 17 函数调用 从左到右 … ( … ) 则函数的执行顺序是:

new (Foo.getName)();

  • 点的优先级(18)比new无参数列表(17)优先级高
  • 当点运算完后又因为有个括号(),此时就是变成new有参数列表(18) 所以直接执行new;

.成员访问(18)->new有参数列表(18)

第六问

new Foo().getName();

该执行顺序可以看做是:

(new Foo()).getName();

  • 点的优先级和 new 有参数列表是相同的,按从左向右的顺序执行

new有参数列表(18)->.成员访问(18)->()函数调用(17);

第七问

new new Foo().getName();

同样是运算符优先级问题.

new ((new Foo()).getName)();

new有参数列表(18)->new有参数列表(18);

先初始化Foo的实例化对象,然后将其原型上的getName函数作为构造函数再次new,所以最终结果为3;

答案

function Foo() {    getName = function () { alert (1); };    return this;}Foo.getName = function () { alert (2);};Foo.prototype.getName = function () { alert (3);};var getName = function () { alert (4);};function getName() { alert (5);}//答案:Foo.getName();//2getName();//4Foo().getName();//1getName();//1new Foo.getName();//2new Foo().getName();//3new new Foo().getName();//3

 

2.判断一个对象是不是数组?共有几种方式?

解析: typeof: 只能区分原始类型的函数

并不能进一步细致区分出对象的类型名;

方法如下:

判断爹(原型对象)

var bool=父对象.isPrototypeOf(子对象);

判断妈(构造函数) 之一

obj.constructor===构造函数; 问题: constructor 是隐藏属性,并不推荐使用;

判断妈(构造函数) 之二

obj instanceof 构造函数; 强调: 以上两种方式都不仅检查直接的父类型,也检查是不是整个原型链的

验DNA

原理: 在每个对象内都有一个隐藏属性 Class

属性: class保存了对象创建时的最初类型;不会随着继承关系的改变而改变;
思路: 只有Object.prototype 中最原始 toString() 才能输出 class 属性 返回值: [object class属性值]; //Object Array Date

易错点: 使用子对象.toString(), 容易被父对象中的toString()所重写

解决: 用call 强行调用Object.prototype中的 toString(); Object.prototype.toString.call(obj);

isArray()

作用: 专门判断任何一个对象是不是Array类型 语法: var bool=Array.isArray(obj); ** 第四种 第五种方法都是严格验证 **

鄙视二 何时函数定义在原型对象中,何时将函数直接定义在构造函数上?

答: 如果只允许指定类型的子对象才能使用的函数,必须放在原型对象中,继承使用 如果希望不限制类型,所有对象都使用的函数,可以直接放在构造函数上: 比如 : sort() push() isArray() Array.prototype.sort(); Array.prototype.push(); Array.isArray();

 

3.this关键字

this到底是一种什么机制?

this是在运行时进行绑定的,并不是在编写的时候进行绑定的,它的上下文取决于函数调用时的各种条件.this的绑定和函数声明的位置没有任何关系,只取决于函数调用时的方式也被称为调用位置.
当函数在调用时, 会创建一个活动记录(AO, 也被称作为上下文). 这个记录会包含函数在哪里被调用(调用栈), 函数的调用方式, 传入的参数等信息. this就是记录这一属性的, 会在函数执行的过程中用到.

一. 调用位置

调用位置就是函数在代码中被调用的位置(而不是声明的位置). 来个小例子, 分析下调用栈和调用位置:

function baz(){    //当前的调用栈是: baz    //因此, 当前调用位置是全局作用域      console.log("baz");      bar();  // bar的调用位置 }function bar(){    //当前的调用栈是: baz -> bar     //因此, 当前的调用位置在baz中    console.log("bar");    foo(); // foo的调用位置}function foo(){    //当前调用栈是: baz -> bar -> foo    //因此,当前的调用位置在foo中    console.log("foo");}baz(); //  baz的调用位置

真正地分析出函数的调用位置, 就可以理解this的绑定了.

二.绑定规则

首先需要找到调用位置, 然后判断需要应用下面四条规则中的那一条.

1.默认规则

这是最常用的函数调用类型: 独立函数使用.

function foo(){    console.log(this.a);}var a = 2;foo();

当调用foo()时, this.a 被解析为全局变量a. 因为函数调用时应用了this的默认绑定,因此this会指向全局.

在代码中, foo()直接使用不带任何修饰的函数引用进行调用, 只能认为是默认绑定.

2.隐式绑定

该规则是调用位置是否具有上下文对象, 或者说是否被某个对象拥有或者包含.

function foo(){    console.log(this.a);}var obj2 = {    a : 42;    foo : foo;  //foo的调用位置};var obj1 = {    a : 2;    obj2 : obj2;};obj1.obj2.foo();  //42

首先, foo()是在全局中被声明, 然后被当做引用属性添加到obj2中. 隐式绑定规则会把函数调用中的this绑定到这个上下文对象中. 因此调用foo时的this被绑定在obj2中.

对象属性引用链中只有上一层或者说最后一层在调用位置中作用.

2.1隐式绑定的丢失

当隐式绑定的函数丢失绑定对象, 就会默认的转为默认绑定. 从而将this绑定到全局对象或者undefined上, 取决于是否为严格模式.

function foo(){    console.log(this.a);}var obj = {    a : 2;    foo : foo;}var bar = obj.foo;var a = "oops, global"; //a为全局对象  bar(); // "oops, global"

在这个例子中:

var bar = obj.foo;

bar只是引用了foo函数的本身, 和obj并没有任何关系. 则bar()就是对foo的直接调用. 因此为默认绑定.

再来一个更有意思的例子:

function foo(){    console.log(this.a);}function doFoo(fn){    //fn 其实引用的是foo    fu(); //调用位置}var obj = {    a : 2;    foo : foo;};var a = "oops, global"; //a为全局对象 doFoo(obj.foo);  // "oops, global"

首先, 在全局作用域中声明了foo的函数; 然后被添加进obj的对象中;然后doFoo函数将foo这个函数作为参数传入进去, 并在其内部进行调用.

在这个例子中, 参数传递被作为一种隐式的赋值.
另外, 在回调函数中, 丢失this的绑定也是比较常见的现象.

3.显式绑定

当我们不想在对象的内部包含函数引用,而想在某个对象上强制调用函数?

为此,JavaScript提供了call(...), apply(...)和bind(...)方法.
call()/apply()/bind():

  1. call()/apply(): 立刻调用函数, 并临时替换函数中的this为指定对象
    如何:
    • fun.call(obj,参数值1,参数值2,...)
    • fun.apply(obj.数组)
  2. bind(): 基于原函数, 创建一个新函数, 永久绑定this为指定对象
    如何: fun.(obj,参数值,...)
    • 创建一个和fun完全一样的函数
    • 永久绑定fun中的this为obj
    • 永久绑定部分参数值

此外, 在JavaScript语言和宿主环境中许多的新的内置函数, 其作用和bind(...)一样, 去报你的回调函数使用指定的this. 比如 [1,2,3].forEach(for, obj ); 等等...

4.new绑定

new可以影响函数调用时this绑定行为的方法, 我们称之为new绑定.

"构造函数"是类中的一些特殊方法, 使用new初始化时会调用类中的构造函数. 通常的形式是这样的:

something = new MyClass(...);

构造函数:

在JavaScript中, 构造函数只是一些使用new操作符时被调用的函数. 它们并不会属于某个类, 也不会实例化一个类. 实际上, 它们甚至都不能说是一种特殊的函数类型, 它们只是被new操作符调用的普通函数而已.

在使用new来调用函数, 或者说是发生构造函数的调用时, 会自动执行下面的操作.

  1. 创建(或者说构造)一个全新的对象.
  2. 这个新对象会被执行[[prototype]]连接.
  3. 这个新对象会绑定到函数调用的this.
  4. 如果没有返回其他对象, 那么new表达式中的函数会自动的返回这个新对象.

三.判断this

当判断先根据绑定的优先级来判断函数的调用位置是相应的哪条规则. 可以按照下面的顺序进行判断:

  1. 函数是否存在new中调用? 是则this绑定的是创建的对象.
var bar = new foo();
  1. 函数是否通过call, apply (显式的绑定)? 如果是的话, this绑定的是指定的对象.
var bar = foo.call(obj2);
  1. 函数是否在某个上下文对象中调用(隐式绑定)? 如果是的话, this绑定在对应的上下文对象中.
var bar = obj1.foo();
  1. 如果以上都不是的话, 使用的为默认绑定, 如果在严格模式下, 就绑定到undefined, 否则为全局对象.
var bar = foo();

 

4.new 关键字

对象是某个特定引用对象的实例.新对象使用new操作符后面跟一个构造函数来创建的.构造函数本身就是一个普通函数,只不过该函数是由创建新对象的目的而被定义.

在创建Object实例的方式有两种:

  • 使用new操作符后跟 Object构造函数. 如下图所示;
var person = new Object();person.name = "nico";persom.age=12;
  • 使用对象字面量表示法,如下所示:
var person = {	name : "nico";	age : 12;}

下面来一个小小例子:

function Person(name,age){	this.name=name;	this.age=age;	this.sayName=function(){		alert(this.name);	};}	var person=new Person("张三",20);  //此处为 构造对象,构造对象的话,返回的新对象是由解析器自己生成的。  	var person=Person("张三",20);	  //报错 person undefined 此处为普通函数调用,又没有给定返回值,出错。  	person.sayName();

注意:构造函数在没有返回值的情况下,默认返回新对象实例。

则可以得出,

  • 当用new调用构造函数时;
  • 没有new时则为普通函数调用.在此种情况,函数内部的this,指向的是window ,函数执行过后,并没有返回值,那么就默认返回一个undefined.但是在控制台中可以看到,window对象中,挂载了age,sayName等变量。

对于return回的内容:

如果函数返回值为常规意义上的值类型(Number、String、Boolean)时,new 函数将会返回一个该函数的实例对象,而如果函数返回一个引用类型(Object、Array、Function),虽然new函数与直接调用函数产生的结果等同,但是是两个不同的过程,一个是构造对象、一个是函数调用。

补充一点

构造函数:

在JavaScript中, 构造函数只是一些使用new操作符时被调用的函数. 它们并不会属于某个类, 也不会实例化一个类. 实际上, 它们甚至都不能说是一种特殊的函数类型, 它们只是被new操作符调用的普通函数而已.

在使用new来调用函数, 或者说是发生构造函数的调用时, 会自动执行下面的操作.

  1. 创建(或者说构造)一个全新的对象.
  2. 这个新对象会被执行[[prototype]]连接.
  3. 这个新对象会绑定到函数调用的this.
  4. 如果没有返回其他对象, 那么new表达式中的函数会自动的返回这个新对象.

 

5.javascript 常用的数组API

  • forEach() 方法对每一个元素执行一次提供的函数
  • map() 方法创建一个新数组, 其结果是该数组都执行一次函数, 原数组保持不变
  • filter() 方法使执行测试数组的每一个函数, 并放回通过元素的新数组
  • some() 方法测试该数组有元素通过了指定函数的测试, 如果有返回true, 否则, false
  • every() 方法测试该数组全部元素通过了指定函数的测试, 如果有返回true, 否则, false
  • find() 找出符合条件的第一个值,并返回

forEach()遍历数组:

var arr= ["a","b","c"];arr.forEach(function(el,index){	console.log(el,index)})

结果:

a 0b 1c 2

map()返回新数组, 为当前元素加字符串m

arr.map(function(el,index){	return el+="m"})

则新数组为: ["am", "bm", "cm"]; 原数组不变: ["a", "b", "c"];

filter()对于元素进行过滤:

var arr = [12, 5, 8, 130, 44];arr.filter(function(value){    return value>10});

也等价于:

arr.filter(item=>item>10)

some()判断元素是否有符合条件,返回布尔值

var arr = [12, 5, 8, 130, 44];arr.some(function(value){                   // true    return value>10});

every()判断元素是不是全部符合条件, 返回布尔值

var arr = [12, 5, 8, 130, 44];arr.every(function(value){                  // false    return value>10});

find()找出符合条件的第一个值,并返回

var ages = [3,12,4165,131];ages.find(age=>age>=18)                     //4165

reduce():遍历数组中的每个值,汇总出一个最终的结果

语法:

var result = arr.reduce(function(prev,val,i,arr){  //prev   return prve+val;},default)

其中,匿名函数最多可放四个参数,后面两个参数可以省略;default可作为默认值传入参数运算,也可省略;

var arr = [1,2,3,4,5];arr.reduce(function(a,b){return a+b;},10); //输出25;

节点迭代器:Nodelterator

鄙视题:定义函数,遍历指定父节点下所有子元素 HTML文件如下:

			遍历节点树		
Hello World !

方法如下:

  • 方法一:递归方式
//Step1: 定义函数,仅遍历指定父元素下的直接子元素function getChildren1(parent){  console.log(parent.nodeName);  var children=parent.children;  for(var i=0,len=children.length;i
  • 方法二:循环方式:

    创建元素节点迭代器 使用doucment.createNodeIterator()方法,创建它的新实例.这个方法接受四个参数;

    • root: 想要作为搜素起点的树中节点
    • whatToShow: 表示要访问哪些节点的数字代码
    • filter: 该位置表示一个NodeFilter对象,或者一个表示应该接受还是拒绝某种特定节点的函数
    • entityReferenceExpansion: 布尔值, 表示是否要扩展实体引用.这个参数在HTML页面中没有用,因为其中的实体引用不能扩展

    whatToShow常用取值,如下所示:

    • NodeFilter.SHOW_ALL:所有类型节点;
    • NodeFilter.SHOW_ELEMENT:元素;
    • NodeFilter.SHOW_ATTRIBUTE:特性;
    • NodeFilter.SHOW_TEXT:文本;
    • NodeFilter.SHOW_COMMENT;
    • NodeFilter.SHOW_DOCUMENT;
    • NodeFilter.SHOW_DOCUMENT_TYPE;
function getChildren2(parent){  //创建节点迭代器对象:  var iterator=document.createNodeIterator(    parent,   - NodeFilter.SHOW_ELEMENT, null, false     //parent:表示作为搜素起点的树中的节点;    //  - NodeFilter.SHOW_ELEMENT:表示显示元素节点    //null:该位置表示一个NodeFilter对象,    //或者一个表示应该接受还是拒绝某种特定节点的函数,一般写为null;    //false 该位置表示是否扩展实体引用;  );  //反复调用iterator的nextNode方法跳到下一个  do{    var node=iterator.nextNode();    if(node!=null)      console.log(node.nodeName);    else break;  }while(true);}getChildren2(document.body);//测速前,暂时删除方法中的console.logconsole.time("getChildren1");//开始getChildren1(document.body);console.timeEnd("getChildren1");//停止console.time("getChildren2");getChildren2(document.body);console.timeEnd("getChildren2");

 

6.数组 鄙视题

已知如下数组:

var arr = [[1,2,2],[3, 4, 5, 5],[6, 7, 8, 9,[11,12,[12,13,[14]]]],10];

编写一个程序将数组扁平化去并除其中重复部分数据,最终得到一个升序且不重复的数组 思路:先转换成字符串,然后去掉重复部分,排序; arr.toString().split(","); //强制转换为字符串,并拼成数组 //去重

var str=[];for(var i=0;i

完整代码:

var arr = [[1,2,2],[3, 4, 5, 5],[6, 7, 8, 9,[11,12,[12,13,[14]]]],10];arr=arr.toString().split(",");var str=[];for(var i=0;i

运算符鄙视题

在JS中 null>0为false,nul==0为false,为什么null>=0为true?

解析: 至于深层次理论我探讨不了; 但在<<JavaScript高级程序设计>>中这样写道:

  • 50p 关系操作符中, 如果一个操作数是数值,则将另一个操作数转换为一个数值,然后执行数值比较。即在null>0的比较中,会将null默认进行转换,Number(null)为0,然后进行比较,所得结果为false;在null>=0则为true;
  • 52p 相等操作符中, 要比较相等性之前,不能将 null 和 undefined 转换成其他任何值。即在null==0的比较中,结果为false;

数组记忆

判断一个数是不是质数? 原理: 利用对象的快速查找,避免利用循环对数组的遍历,从而提高程序的执行效率.

var isPrime=(function(){  var hash={};  return function(n){    if(!isNaN(n)&&n>1){  //排除不是正整数的情况      if(n<=3) return true; //  排除2和3的情况      else if(n%2==0) return false; //排除偶数的情况      else if(hash[n]!==undefined){        console.log("不用执行for循环...");            //若hash对象中已经有了则直接返回,避免再次判断        return hash[n];      }else{        console.log("执行for循环...");        for(var i=3;i<=Math.sqrt(n);i+=2){          if(n%i==0){            hash[n]=false;            return false;          }        }        hash[n]=true;        return true;      }    }else      throw new Error(        "必须输入>1的数字");  }})();

数组排序

  • 冒泡排序

  • 插入排序

原理: i从1开始向右遍历每一个数 将i位置的元素临时保存在变量t中

var arr=[4,2,5,3,1];function insertSort(arr){  for(var i=1;i
=0&&arr[p]>=t){ arr[p+1]=arr[p]; p--; } arr[p+1]=t; }}insertSort(arr);console.log(arr);//1,2,3,4,5;
  • 快速排序
var arr=[6,3,1,5,4,7,2];function quickSort(arr){  if(arr.length<=1) return arr;  else{    var c=parseInt((arr.length-1)/2);    var cv=arr.splice(c,1)[0];    var left=[],right=[];    for(var i=0;i

 

7.JavaScript 小练习

最近在http://www.codewars.com上练习js的题, 受益颇多;

  1. 求数组中的最小值;
class SmallestIntegerFinder {  findSmallestInt(args) {    return Math.min(...args)  }}
class SmallestIntegerFinder {  findSmallestInt(args) {    return args.sort((a,b)=>a-b)[0];  }}
  1. 从一段英文中,返回对应字符串在字母表的位置;
    e.g.
alphabet_position("The sunset sets at twelve o' clock.");return "20 8 5 19 21 14 19 5 20 19 5 20 19 1 20 20 23 5 12 22 5 15 3 12 15 3 11";

我的答案:

function alphabetPosition(text) {  var result = [];  for(var i=0; i
=97? test-96:test-64; if(we>=1 && we<=26){ result.push(we); } } return result.join(" ");}var a = alphabetPosition("The sunset sets at twelve o' clock.");console.log(a)

缺点: 使用了数组的api,有了一些不必要的操作;

best:

function alphabetPosition(text) {  var result = "";  for (var i = 0; i < text.length; i++){    var code = text.toUpperCase().charCodeAt(i)    if (code > 64 && code < 91) result += (code - 64) + " ";  }  return result.slice(0, result.length-1);}
  1. 去除字符串中多余的字符;

e.g.:

songDecoder("WUBWEWUBAREWUBWUBTHEWUBCHAMPIONSWUBMYWUBFRIENDWUB")

// => WE ARE THE CHAMPIONS MY FRIEND

我的答案:

function songDecoder(song){  return song.replace(/(WUB)+/g," ").trim();}

best:

function songDecoder(song){  return song.split('WUB').filter(Boolean).join(' ');}
  1. 字符串中首字母大写:

自己的方法没做出来, 多多少少有点bug;

大神们的办法:

  • 正则:
String.prototype.toJadenCase = function () {  return this.replace(/(^|\s)[a-z]/g, function(x){ return x.toUpperCase(); });};
  • 纯js:
String.prototype.toJadenCase = function () {   return this.split(" ").map(function(word){    return word.charAt(0).toUpperCase() + word.slice(1);  }).join(" ");}

css方式:

text-transform 值:

  • Capitalize 英文拼音的首字母大写
  • Uppercase 英文拼音字母全大写
  • Lowercase 英文拼音字母全小写
  1.  
accum("abcd");    // "A-Bb-Ccc-Dddd"accum("RqaEzty"); // "R-Qq-Aaa-Eeee-Zzzzz-Tttttt-Yyyyyyy"accum("cwAt");    // "C-Ww-Aaa-Tttt"

我的答案:

function accum(s) {	var str = "";	for (var i = 0; i < s.length; i++) {		var str1 = s[i];		for (var j = 0; j <=i; j++) {			if (j==0) {				str+=str1.toUpperCase();			}else{				str+=str1.toLowerCase();			}		}		str+="-";	}	return str.slice(0,-1);}

大佬就是大佬:

function accum(s) {  return s.split('').map((c, i) => (c.toUpperCase() + c.toLowerCase().repeat(i))).join('-');}
  1. 选取数组中值得长度为4的值;

大佬的方法:

function friend(friends){  return friends.filter(n => n.length === 4)}

我的方法:

function friend(friends){  var arr = [];  for(var a = 0; a

原文 

转载地址:http://ypwii.baihongyu.com/

你可能感兴趣的文章
解决SimpleDateFormat线程安全问题NumberFormatException: multiple points
查看>>
MySQL数据库存储引擎简介
查看>>
处理Maven本地仓库.lastUpdated文件
查看>>
Windows操作系统安装MySQL解压版
查看>>
Java并发编程之Wait和Notify
查看>>
自定义git命令实现一步提交代码到仓库
查看>>
让谁搭车?
查看>>
Maven依赖版本号引发的血案
查看>>
什么是package-info.java
查看>>
Kafka | 请求是怎么被处理的?
查看>>
Java并发编程1-线程池
查看>>
CentOS7,玩转samba服务,基于身份验证的共享
查看>>
计算机网络-网络协议模型
查看>>
计算机网络-OSI各层概述
查看>>
Java--String/StringBuffer/StringBuilder区别
查看>>
mySQL--深入理解事务隔离级别
查看>>
分布式之redis复习精讲
查看>>
数据结构与算法7-栈
查看>>
线性数据结构学习笔记
查看>>
数据结构与算法9-递归
查看>>